From 9d38069fb34e869ad805ff80abae953c9bc051e2 Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Fri, 16 Apr 2021 10:58:37 +0200 Subject: [PATCH 1/9] feat: Add gamepad control of ImGui PoC WIP - Switch between letting the game or dalamud handling input via R1+L1 - Control ImGui windows via the gamepad in accordance to the proposed button mapping --- Dalamud/Game/ClientState/ClientState.cs | 6 + Dalamud/Game/ClientState/GamepadButtons.cs | 24 +++ Dalamud/Game/ClientState/GamepadState.cs | 150 ++++++++++++++++++ .../Game/ClientState/Structs/GamepadInput.cs | 38 +++++ Dalamud/Interface/InterfaceManager.cs | 20 +++ 5 files changed, 238 insertions(+) create mode 100644 Dalamud/Game/ClientState/GamepadButtons.cs create mode 100644 Dalamud/Game/ClientState/GamepadState.cs create mode 100644 Dalamud/Game/ClientState/Structs/GamepadInput.cs diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 9be2d194c..69b5661c9 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -98,6 +98,8 @@ namespace Dalamud.Game.ClientState /// public KeyState KeyState; + public GamepadState GamepadState; + /// /// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc. /// @@ -131,6 +133,8 @@ namespace Dalamud.Game.ClientState this.KeyState = new KeyState(Address, scanner.Module.BaseAddress); + this.GamepadState = new GamepadState(scanner); + this.Condition = new Condition( Address ); this.Targets = new Targets(dalamud, Address); @@ -150,6 +154,7 @@ namespace Dalamud.Game.ClientState } public void Enable() { + this.GamepadState.Enable(); this.PartyList.Enable(); this.setupTerritoryTypeHook.Enable(); } @@ -158,6 +163,7 @@ namespace Dalamud.Game.ClientState this.PartyList.Dispose(); this.setupTerritoryTypeHook.Dispose(); this.Actors.Dispose(); + this.GamepadState.Dispose(); this.dalamud.Framework.OnUpdateEvent -= FrameworkOnOnUpdateEvent; this.dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop; diff --git a/Dalamud/Game/ClientState/GamepadButtons.cs b/Dalamud/Game/ClientState/GamepadButtons.cs new file mode 100644 index 000000000..a3b91711c --- /dev/null +++ b/Dalamud/Game/ClientState/GamepadButtons.cs @@ -0,0 +1,24 @@ +namespace Dalamud.Game.ClientState +{ + public enum GamepadButtons : ushort + { + XINPUT_GAMEPAD_DPAD_UP = 0x0001, + XINPUT_GAMEPAD_DPAD_DOWN = 0x0002, + XINPUT_GAMEPAD_DPAD_LEFT = 0x0004, + XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008, + XINPUT_GAMEPAD_Y = 0x0010, + XINPUT_GAMEPAD_A = 0x0020, + XINPUT_GAMEPAD_X = 0x0040, + XINPUT_GAMEPAD_B = 0x0080, + XINPUT_GAMEPAD_LEFT_1 = 0x0100, + XINPUT_GAMEPAD_LEFT_2 = 0x0200, // The back one + XINPUT_GAMEPAD_LEFT_3 = 0x0400, + XINPUT_GAMEPAD_RIGHT_1 = 0x0800, + XINPUT_GAMEPAD_RIGHT_2 = 0x1000, // The back one + XINPUT_GAMEPAD_RIGHT_3 = 0x2000, + XINPUT_GAMEPAD_START = 0x8000, + XINPUT_GAMEPAD_SELECT = 0x4000, + + + } +} diff --git a/Dalamud/Game/ClientState/GamepadState.cs b/Dalamud/Game/ClientState/GamepadState.cs new file mode 100644 index 000000000..50957ef52 --- /dev/null +++ b/Dalamud/Game/ClientState/GamepadState.cs @@ -0,0 +1,150 @@ +using System; +using System.Windows.Forms; +using Dalamud.Game.ClientState.Structs; +using Dalamud.Hooking; +using OpenGL; + +namespace Dalamud.Game.ClientState +{ + public unsafe class GamepadState + { + + public float ButtonSouth => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_A) > 0 ? 1 : 0; + public float ButtonEast => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_B) > 0 ? 1 : 0; + public float ButtonWest => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_X) > 0 ? 1 : 0; + public float ButtonNorth => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_Y) > 0 ? 1 : 0; + public float DPadDown => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_DOWN) > 0 ? 1 : 0; + public float DPadRight => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_RIGHT) > 0 ? 1 : 0; + public float DPadUp => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_UP) > 0 ? 1 : 0; + public float DPadLeft => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_LEFT) > 0 ? 1 : 0; + public float L1 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_1) > 0 ? 1 : 0; + public float L2 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_2) > 0 ? 1 : 0; + public float L3 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_3) > 0 ? 1 : 0; + public float R1 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_1) > 0 ? 1 : 0; + public float R2 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_2) > 0 ? 1 : 0; + public float R3 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_3) > 0 ? 1 : 0; + public float Start => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_START) > 0 ? 1 : 0; + public float Select => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_SELECT) > 0 ? 1 : 0; + public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0; + public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0; + public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0; + public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0; + public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0; + public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0; + public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0; + public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0; + + public float Tapped(GamepadButtons button) + => (this.buttonsTapped & (ushort)button) > 0 ? 1 : 0; + + public float Holding(GamepadButtons button) + => (this.buttonsHolding & (ushort)button) > 0 ? 1 : 0; + + public float Released(GamepadButtons button) + => (this.buttonsReleased & (ushort)button) > 0 ? 1 : 0; + + private delegate int ControllerPoll(IntPtr controllerInput); + + private Hook controllerPoll; + //private GamepadInput* _gamePadInput; + private bool isDisposed; + + private int leftStickX; + private int leftStickY; + private int rightStickX; + private int rightStickY; + private ushort buttons; + private ushort buttonsTapped; + private ushort buttonsReleased; + private ushort buttonsHolding; + private bool imGuiMode; + + + public GamepadState(SigScanner scanner) + { + const string controllerPollSignature = + "40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"; + this.controllerPoll = new Hook( + scanner.ScanText(controllerPollSignature), + (ControllerPoll) ControllerPollDetour); + } + + + private unsafe int ControllerPollDetour(IntPtr gamepadInput) + { + //this._gamePadInput =(GamepadInput*) gamepadInput; + var original = this.controllerPoll.Original(gamepadInput); + var input = (GamepadInput*)gamepadInput; + if ( + (input->ButtonFlag_Holding & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_1) > 0 + && (input->ButtonFlag & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_1) > 0) + { + this.imGuiMode = !this.imGuiMode; + if (!this.imGuiMode) + { + this.leftStickX = 0; + this.leftStickY = 0; + this.rightStickY = 0; + this.rightStickX = 0; + this.buttons = 0; + this.buttonsTapped = 0; + this.buttonsReleased = 0; + this.buttonsHolding = 0; + } + } + + if (this.imGuiMode) + { + this.leftStickX = input->LeftStickX; + this.leftStickY = input->LeftStickY; + this.rightStickX = input->RightStickX; + this.rightStickY = input->RightStickY; + this.buttons = input->ButtonFlag; + this.buttonsTapped = input->ButtonFlag_Tap; + this.buttonsReleased = input->ButtonFlag_Release; + this.buttonsHolding = input->ButtonFlag_Holding; + + input->LeftStickX = 0; + input->LeftStickY = 0; + input->RightStickX = 0; + input->RightStickY = 0; + input->ButtonFlag = 0; + input->ButtonFlag_Tap = 0; + input->ButtonFlag_Release = 0; + input->ButtonFlag_Holding = 0; + } + + // Not so sure about the return value, does not seem to matter if we return the + // original, zero or do the work adjusting the bits. + return original; + } + + public void Enable() + { + this.controllerPoll.Enable(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (this.isDisposed) return; + if (disposing) + { + this.controllerPoll?.Disable(); + this.controllerPoll?.Dispose(); + } + + this.isDisposed = true; + } + + ~GamepadState() + { + Dispose(false); + } + } +} diff --git a/Dalamud/Game/ClientState/Structs/GamepadInput.cs b/Dalamud/Game/ClientState/Structs/GamepadInput.cs new file mode 100644 index 000000000..f9bdd1c00 --- /dev/null +++ b/Dalamud/Game/ClientState/Structs/GamepadInput.cs @@ -0,0 +1,38 @@ +using System.Runtime.InteropServices; + +namespace Dalamud.Game.ClientState.Structs +{ + // It is bigger, but I dunno how big in real + [StructLayout(LayoutKind.Explicit)] + public struct GamepadInput + { + // Each stick is -99 till 99 + [FieldOffset(0x88)] + public int LeftStickX; + + [FieldOffset(0x8C)] + public int LeftStickY; + + [FieldOffset(0x90)] + public int RightStickX; + + [FieldOffset(0x94)] + public int RightStickY; + + // Seems to be source of true, instant population, keeps value while hold. + [FieldOffset(0x98)] + public ushort ButtonFlag; // bitfield + + // Gets populated only if released after a short tick + [FieldOffset(0x9C)] + public ushort ButtonFlag_Tap; // bitfield + + // Gets populated on button release + [FieldOffset(0xA0)] + public ushort ButtonFlag_Release; // bitfield + + // Gets populated after a tick and keeps being set while button is held + [FieldOffset(0xA4)] + public ushort ButtonFlag_Holding; // bitfield + } +} diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index 854fc9dbe..78681cf86 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -291,6 +291,9 @@ namespace Dalamud.Interface ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; } + //TODO (Chiv) Addition + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); Log.Information("[IM] Scene & ImGui setup OK!"); @@ -460,6 +463,23 @@ namespace Dalamud.Interface this.dalamud.ClientState.KeyState.ClearAll(); } + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.ButtonSouth; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.ButtonEast; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = this.dalamud.ClientState.GamepadState.ButtonNorth; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = this.dalamud.ClientState.GamepadState.ButtonWest; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = this.dalamud.ClientState.GamepadState.DPadLeft; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = this.dalamud.ClientState.GamepadState.DPadRight; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = this.dalamud.ClientState.GamepadState.DPadUp; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = this.dalamud.ClientState.GamepadState.DPadDown; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = this.dalamud.ClientState.GamepadState.LeftStickLeft; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = this.dalamud.ClientState.GamepadState.LeftStickRight; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = this.dalamud.ClientState.GamepadState.LeftStickUp; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = this.dalamud.ClientState.GamepadState.LeftStickDown; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = this.dalamud.ClientState.GamepadState.L1; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = this.dalamud.ClientState.GamepadState.R1; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = this.dalamud.ClientState.GamepadState.L2; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = this.dalamud.ClientState.GamepadState.R2; + // TODO: mouse state? } From 76bf0bc52dc8366dcd5b3d3c7e16b5a93593b549 Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Sun, 18 Apr 2021 15:18:26 +0200 Subject: [PATCH 2/9] feat: Add support for gamepad control of ImGui Alongside a settings option and the ability for plugins to query gamepad state. PLugins cannot, however, as of now set an input as 'handled' and block it for the game. Whether game or ImGui is receiving gamepad input can be be checked via `(ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0` --- Dalamud/Configuration/DalamudConfiguration.cs | 5 + Dalamud/Game/ClientState/ClientState.cs | 5 +- .../ClientState/ClientStateAddressResolver.cs | 11 +- Dalamud/Game/ClientState/GamepadButtons.cs | 96 +++++- Dalamud/Game/ClientState/GamepadState.cs | 302 ++++++++++++------ .../Game/ClientState/Structs/GamepadInput.cs | 46 ++- Dalamud/Interface/DalamudSettingsWindow.cs | 20 +- Dalamud/Interface/InterfaceManager.cs | 66 ++-- 8 files changed, 396 insertions(+), 155 deletions(-) diff --git a/Dalamud/Configuration/DalamudConfiguration.cs b/Dalamud/Configuration/DalamudConfiguration.cs index dc9ea65ed..46ef9f208 100644 --- a/Dalamud/Configuration/DalamudConfiguration.cs +++ b/Dalamud/Configuration/DalamudConfiguration.cs @@ -122,6 +122,11 @@ namespace Dalamud.Configuration /// public bool IsDisableViewport { get; set; } = true; + /// + /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui. + /// + public bool IsGamepadNavigationEnabled { get; set; } = true; + /// /// Load a configuration from the provided path. /// diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 69b5661c9..f5215cb1c 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -98,6 +98,9 @@ namespace Dalamud.Game.ClientState /// public KeyState KeyState; + /// + /// Provides access to the button state of gamepad buttons in game. + /// public GamepadState GamepadState; /// @@ -133,7 +136,7 @@ namespace Dalamud.Game.ClientState this.KeyState = new KeyState(Address, scanner.Module.BaseAddress); - this.GamepadState = new GamepadState(scanner); + this.GamepadState = new GamepadState(this.Address); this.Condition = new Condition( Address ); diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index adede5248..94591bf4e 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -16,7 +16,14 @@ namespace Dalamud.Game.ClientState public IntPtr SetupTerritoryType { get; private set; } //public IntPtr SomeActorTableAccess { get; private set; } //public IntPtr PartyListUpdate { get; private set; } - + + /// + /// Game function which polls the gamepads for data. + /// + /// Called every frame, even when `Enable Gamepad` is off in the settings. + /// + public IntPtr GamepadPoll { get; private set; } + public IntPtr ConditionFlags { get; private set; } protected override void Setup64Bit(SigScanner sig) { @@ -38,6 +45,8 @@ namespace Dalamud.Game.ClientState ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30"); TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3); + + this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); } } } diff --git a/Dalamud/Game/ClientState/GamepadButtons.cs b/Dalamud/Game/ClientState/GamepadButtons.cs index a3b91711c..e1db685e0 100644 --- a/Dalamud/Game/ClientState/GamepadButtons.cs +++ b/Dalamud/Game/ClientState/GamepadButtons.cs @@ -1,24 +1,88 @@ namespace Dalamud.Game.ClientState { + /// + /// Bitmask of the Button ushort used by the game. + /// public enum GamepadButtons : ushort { - XINPUT_GAMEPAD_DPAD_UP = 0x0001, - XINPUT_GAMEPAD_DPAD_DOWN = 0x0002, - XINPUT_GAMEPAD_DPAD_LEFT = 0x0004, - XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008, - XINPUT_GAMEPAD_Y = 0x0010, - XINPUT_GAMEPAD_A = 0x0020, - XINPUT_GAMEPAD_X = 0x0040, - XINPUT_GAMEPAD_B = 0x0080, - XINPUT_GAMEPAD_LEFT_1 = 0x0100, - XINPUT_GAMEPAD_LEFT_2 = 0x0200, // The back one - XINPUT_GAMEPAD_LEFT_3 = 0x0400, - XINPUT_GAMEPAD_RIGHT_1 = 0x0800, - XINPUT_GAMEPAD_RIGHT_2 = 0x1000, // The back one - XINPUT_GAMEPAD_RIGHT_3 = 0x2000, - XINPUT_GAMEPAD_START = 0x8000, - XINPUT_GAMEPAD_SELECT = 0x4000, + /// + /// Digipad up. + /// + DpadUp = 0x0001, + /// + /// Digipad down. + /// + DpadDown = 0x0002, + /// + /// Digipad left. + /// + DpadLeft = 0x0004, + + /// + /// Digipad right. + /// + DpadRight = 0x0008, + + /// + /// North action button. Triangle on PS, Y on Xbox. + /// + North = 0x0010, + + /// + /// South action button. Cross on PS, A on Xbox. + /// + South = 0x0020, + + /// + /// West action button. Square on PS, X on Xbos. + /// + West = 0x0040, + + /// + /// East action button. Circle on PS, B on Xbox. + /// + East = 0x0080, + + /// + /// First button on left shoulder side. + /// + L1 = 0x0100, + + /// + /// Second button on left shoulder side. Analog input lost in this bitmask. + /// + L2 = 0x0200, + + /// + /// Press on left analogue stick. + /// + L3 = 0x0400, + + /// + /// First button on right shoulder. + /// + R1 = 0x0800, + + /// + /// Second button on right shoulder. Analog input lost in this bitmask. + /// + R2 = 0x1000, + + /// + /// Press on right analogue stick. + /// + R3 = 0x2000, + + /// + /// Button on the right inner side of the controller. Options on PS, Start on Xbox. + /// + Start = 0x8000, + + /// + /// Button on the left inner side of the controller. ??? on PS, Back on Xbox. + /// + Select = 0x4000, } } diff --git a/Dalamud/Game/ClientState/GamepadState.cs b/Dalamud/Game/ClientState/GamepadState.cs index 50957ef52..0c33f28bf 100644 --- a/Dalamud/Game/ClientState/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamepadState.cs @@ -1,150 +1,240 @@ using System; -using System.Windows.Forms; + using Dalamud.Game.ClientState.Structs; using Dalamud.Hooking; -using OpenGL; +using ImGuiNET; namespace Dalamud.Game.ClientState { + /// + /// Exposes the game gamepad state to dalamud. + /// + /// Will block game's gamepad input if is set. + /// public unsafe class GamepadState { + private readonly Hook gamepadPoll; - public float ButtonSouth => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_A) > 0 ? 1 : 0; - public float ButtonEast => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_B) > 0 ? 1 : 0; - public float ButtonWest => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_X) > 0 ? 1 : 0; - public float ButtonNorth => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_Y) > 0 ? 1 : 0; - public float DPadDown => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_DOWN) > 0 ? 1 : 0; - public float DPadRight => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_RIGHT) > 0 ? 1 : 0; - public float DPadUp => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_UP) > 0 ? 1 : 0; - public float DPadLeft => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_DPAD_LEFT) > 0 ? 1 : 0; - public float L1 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_1) > 0 ? 1 : 0; - public float L2 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_2) > 0 ? 1 : 0; - public float L3 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_3) > 0 ? 1 : 0; - public float R1 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_1) > 0 ? 1 : 0; - public float R2 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_2) > 0 ? 1 : 0; - public float R3 => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_3) > 0 ? 1 : 0; - public float Start => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_START) > 0 ? 1 : 0; - public float Select => (this.buttonsTapped & (ushort)GamepadButtons.XINPUT_GAMEPAD_SELECT) > 0 ? 1 : 0; - public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0; - public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0; - public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0; - public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0; - public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0; - public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0; - public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0; - public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0; - - public float Tapped(GamepadButtons button) - => (this.buttonsTapped & (ushort)button) > 0 ? 1 : 0; - - public float Holding(GamepadButtons button) - => (this.buttonsHolding & (ushort)button) > 0 ? 1 : 0; - - public float Released(GamepadButtons button) - => (this.buttonsReleased & (ushort)button) > 0 ? 1 : 0; - - private delegate int ControllerPoll(IntPtr controllerInput); - - private Hook controllerPoll; - //private GamepadInput* _gamePadInput; private bool isDisposed; private int leftStickX; private int leftStickY; private int rightStickX; private int rightStickY; - private ushort buttons; - private ushort buttonsTapped; - private ushort buttonsReleased; - private ushort buttonsHolding; - private bool imGuiMode; - - public GamepadState(SigScanner scanner) + /// + /// Initializes a new instance of the class. + /// + /// Resolver knowing the pointer to the GamepadPoll function. + public GamepadState(ClientStateAddressResolver resolver) { - const string controllerPollSignature = - "40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"; - this.controllerPoll = new Hook( - scanner.ScanText(controllerPollSignature), - (ControllerPoll) ControllerPollDetour); + this.gamepadPoll = new Hook( + resolver.GamepadPoll, + (ControllerPoll)this.GamepadPollDetour); } - - private unsafe int ControllerPollDetour(IntPtr gamepadInput) + /// + /// Finalizes an instance of the class. + /// + ~GamepadState() { - //this._gamePadInput =(GamepadInput*) gamepadInput; - var original = this.controllerPoll.Original(gamepadInput); + this.Dispose(false); + } + + private delegate int ControllerPoll(IntPtr controllerInput); + +#if DEBUG + /// + /// Gets the pointer to the current instance of the GamepadInput struct. + /// + public IntPtr GamepadInput { get; private set; } +#endif + + /// + /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0; + + /// + /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsPressed { get; private set; } + + /// + /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsRaw { get; private set; } + + /// + /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsReleased { get; private set; } + + /// + /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsRepeat { get; private set; } + + /// + /// Gets whether has been pressed. + /// + /// Only true on first frame of the press. + /// + /// The button to check for. + /// 1 if pressed, 0 otherwise. + public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets whether is being pressed. + /// + /// True in intervals if button is held down. + /// + /// The button to check for. + /// 1 if still pressed during interval, 0 otherwise or in between intervals. + public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets whether has been released. + /// + /// Only true the frame after release. + /// + /// The button to check for. + /// 1 if released, 0 otherwise. + public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets the raw state of . + /// + /// Is set the entire time a button is pressed down. + /// + /// The button to check for. + /// 1 the whole time button is pressed, 0 otherwise. + public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; + + /// + /// Enables the hook of the GamepadPoll function. + /// + public void Enable() + { + this.gamepadPoll.Enable(); + } + + /// + /// Disposes this instance, alongside its hooks. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + private int GamepadPollDetour(IntPtr gamepadInput) + { +#if DEBUG + this.GamepadInput = gamepadInput; +#endif + var original = this.gamepadPoll.Original(gamepadInput); var input = (GamepadInput*)gamepadInput; - if ( - (input->ButtonFlag_Holding & (ushort)GamepadButtons.XINPUT_GAMEPAD_RIGHT_1) > 0 - && (input->ButtonFlag & (ushort)GamepadButtons.XINPUT_GAMEPAD_LEFT_1) > 0) - { - this.imGuiMode = !this.imGuiMode; - if (!this.imGuiMode) - { - this.leftStickX = 0; - this.leftStickY = 0; - this.rightStickY = 0; - this.rightStickX = 0; - this.buttons = 0; - this.buttonsTapped = 0; - this.buttonsReleased = 0; - this.buttonsHolding = 0; - } - } + this.leftStickX = input->LeftStickX; + this.leftStickY = input->LeftStickY; + this.rightStickX = input->RightStickX; + this.rightStickY = input->RightStickY; + this.ButtonsRaw = input->ButtonsRaw; + this.ButtonsPressed = input->ButtonsPressed; + this.ButtonsReleased = input->ButtonsReleased; + this.ButtonsRepeat = input->ButtonsRepeat; - if (this.imGuiMode) + if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) { - this.leftStickX = input->LeftStickX; - this.leftStickY = input->LeftStickY; - this.rightStickX = input->RightStickX; - this.rightStickY = input->RightStickY; - this.buttons = input->ButtonFlag; - this.buttonsTapped = input->ButtonFlag_Tap; - this.buttonsReleased = input->ButtonFlag_Release; - this.buttonsHolding = input->ButtonFlag_Holding; - input->LeftStickX = 0; input->LeftStickY = 0; input->RightStickX = 0; input->RightStickY = 0; - input->ButtonFlag = 0; - input->ButtonFlag_Tap = 0; - input->ButtonFlag_Release = 0; - input->ButtonFlag_Holding = 0; + + // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` + // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). + // It does block, however, all input to the game. + // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2 + // and the digipad (in some situations, but thankfully not in menus) functional. + // We can either: + // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or + // (b) ignore it as so far it seems only a 'visual' error + // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input, + // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them + // because of the other blocked input) + // `ButtonPressed` is pretty useful so we opt-in to (b). + // This is debatable. + // ImGui itself does not care either way as it uses the Raw values and does its own state handling. + // input->ButtonsRaw &= (ushort)~GamepadButtons.L2; + // input->ButtonsRaw &= (ushort)~GamepadButtons.R2; + // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadDown; + // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadLeft; + // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadUp; + // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadRight; + input->ButtonsPressed = 0; + input->ButtonsReleased = 0; + input->ButtonsRepeat = 0; + return 0; } - // Not so sure about the return value, does not seem to matter if we return the + // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the // original, zero or do the work adjusting the bits. return original; } - public void Enable() - { - this.controllerPoll.Enable(); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - private void Dispose(bool disposing) { if (this.isDisposed) return; if (disposing) { - this.controllerPoll?.Disable(); - this.controllerPoll?.Dispose(); + this.gamepadPoll?.Disable(); + this.gamepadPoll?.Dispose(); } this.isDisposed = true; } - - ~GamepadState() - { - Dispose(false); - } } } diff --git a/Dalamud/Game/ClientState/Structs/GamepadInput.cs b/Dalamud/Game/ClientState/Structs/GamepadInput.cs index f9bdd1c00..ce7440b61 100644 --- a/Dalamud/Game/ClientState/Structs/GamepadInput.cs +++ b/Dalamud/Game/ClientState/Structs/GamepadInput.cs @@ -2,37 +2,63 @@ namespace Dalamud.Game.ClientState.Structs { - // It is bigger, but I dunno how big in real + /// + /// Struct which gets populated by polling the gamepads. + /// + /// Has an array of gamepads, among many other things (here not mapped). + /// All we really care about is the final data which the game uses to determine input. + /// + /// The size is definitely bigger than only the following fields but I do not know how big. + /// [StructLayout(LayoutKind.Explicit)] public struct GamepadInput { - // Each stick is -99 till 99 + /// + /// Left analogue stick's horizontal value, -99 for left, 99 for right. + /// [FieldOffset(0x88)] public int LeftStickX; + /// + /// Left analogue stick's vertical value, -99 for down, 99 for up. + /// [FieldOffset(0x8C)] public int LeftStickY; + /// + /// Right analogue stick's horizontal value, -99 for left, 99 for right. + /// [FieldOffset(0x90)] public int RightStickX; + /// + /// Right analogue stick's vertical value, -99 for down, 99 for up. + /// [FieldOffset(0x94)] public int RightStickY; - // Seems to be source of true, instant population, keeps value while hold. + /// + /// Raw input, set the whole time while a button is held. See for the mapping. + /// [FieldOffset(0x98)] - public ushort ButtonFlag; // bitfield + public ushort ButtonsRaw; // bitfield - // Gets populated only if released after a short tick + /// + /// Button pressed, set once when the button is pressed. See for the mapping. + /// [FieldOffset(0x9C)] - public ushort ButtonFlag_Tap; // bitfield + public ushort ButtonsPressed; // bitfield - // Gets populated on button release + /// + /// Button released input, set once right after the button is not hold anymore. See for the mapping. + /// [FieldOffset(0xA0)] - public ushort ButtonFlag_Release; // bitfield + public ushort ButtonsReleased; // bitfield - // Gets populated after a tick and keeps being set while button is held + /// + /// Repeatedly emits the held button input in fixed intervals. See for the mapping. + /// [FieldOffset(0xA4)] - public ushort ButtonFlag_Holding; // bitfield + public ushort ButtonsRepeat; // bitfield } } diff --git a/Dalamud/Interface/DalamudSettingsWindow.cs b/Dalamud/Interface/DalamudSettingsWindow.cs index 152e7ff32..c350e9e13 100644 --- a/Dalamud/Interface/DalamudSettingsWindow.cs +++ b/Dalamud/Interface/DalamudSettingsWindow.cs @@ -23,7 +23,7 @@ namespace Dalamud.Interface { this.dalamud = dalamud; - this.Size = new Vector2(740, 500); + this.Size = new Vector2(740, 525); this.SizeCondition = ImGuiCond.FirstUseEver; this.dalamudMessagesChatType = this.dalamud.Configuration.GeneralChatType; @@ -38,6 +38,7 @@ namespace Dalamud.Interface this.doDocking = this.dalamud.Configuration.IsDocking; this.doViewport = !this.dalamud.Configuration.IsDisableViewport; + this.doGamepad = this.dalamud.Configuration.IsGamepadNavigationEnabled; this.doPluginTest = this.dalamud.Configuration.DoPluginTest; this.thirdRepoList = this.dalamud.Configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); @@ -133,6 +134,7 @@ namespace Dalamud.Interface private bool doToggleUiHideDuringGpose; private bool doDocking; private bool doViewport; + private bool doGamepad; private List thirdRepoList; private bool printPluginsWelcomeMsg; @@ -228,6 +230,9 @@ namespace Dalamud.Interface ImGui.Checkbox(Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"), ref this.doDocking); ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows.")); + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Enable navigation of ImGui windows via gamepad."), ref this.doGamepad); + ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and ImGui navigation via L1+L3.")); + ImGui.EndTabItem(); } @@ -378,6 +383,7 @@ namespace Dalamud.Interface this.dalamud.Configuration.ToggleUiHideDuringGpose = this.doToggleUiHideDuringGpose; this.dalamud.Configuration.IsDocking = this.doDocking; + this.dalamud.Configuration.IsGamepadNavigationEnabled = this.doGamepad; // This is applied every frame in InterfaceManager::CheckViewportState() this.dalamud.Configuration.IsDisableViewport = !this.doViewport; @@ -392,6 +398,18 @@ namespace Dalamud.Interface ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; } + // NOTE (Chiv) Toggle gamepad navigation via setting + if (!this.dalamud.Configuration.IsGamepadNavigationEnabled) + { + ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; + } + else + { + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; + } + this.dalamud.Configuration.DoPluginTest = this.doPluginTest; this.dalamud.Configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList(); diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index 78681cf86..0b67f9a63 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; using Dalamud.Game; +using Dalamud.Game.ClientState; using Dalamud.Game.Internal.DXGI; using Dalamud.Hooking; using EasyHook; @@ -291,9 +292,18 @@ namespace Dalamud.Interface ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; } - //TODO (Chiv) Addition - ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; - + // NOTE (Chiv) Toggle gamepad navigation via setting + if (!this.dalamud.Configuration.IsGamepadNavigationEnabled) + { + ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; + } + else + { + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; + } + ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); Log.Information("[IM] Scene & ImGui setup OK!"); @@ -463,24 +473,40 @@ namespace Dalamud.Interface this.dalamud.ClientState.KeyState.ClearAll(); } - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.ButtonSouth; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.ButtonEast; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = this.dalamud.ClientState.GamepadState.ButtonNorth; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = this.dalamud.ClientState.GamepadState.ButtonWest; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = this.dalamud.ClientState.GamepadState.DPadLeft; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = this.dalamud.ClientState.GamepadState.DPadRight; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = this.dalamud.ClientState.GamepadState.DPadUp; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = this.dalamud.ClientState.GamepadState.DPadDown; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = this.dalamud.ClientState.GamepadState.LeftStickLeft; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = this.dalamud.ClientState.GamepadState.LeftStickRight; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = this.dalamud.ClientState.GamepadState.LeftStickUp; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = this.dalamud.ClientState.GamepadState.LeftStickDown; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = this.dalamud.ClientState.GamepadState.L1; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = this.dalamud.ClientState.GamepadState.R1; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = this.dalamud.ClientState.GamepadState.L2; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = this.dalamud.ClientState.GamepadState.R2; - // TODO: mouse state? + + var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0; + + // NOTE (Chiv) Activate ImGui navigation via L1+L3 press + // (mimicking to how mouse navigation is activated via L1+R3 press in game). + if (gamepadEnabled + && this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L1) > 0 + && this.dalamud.ClientState.GamepadState.Pressed(GamepadButtons.L3) > 0) + { + ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad; + this.dalamud.DalamudUi.TogglePluginInstaller(); + } + + if (gamepadEnabled + && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) + { + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.South); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.East); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.North); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.West); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadLeft); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadRight); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadUp); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.DpadDown); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = this.dalamud.ClientState.GamepadState.LeftStickLeft; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = this.dalamud.ClientState.GamepadState.LeftStickRight; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = this.dalamud.ClientState.GamepadState.LeftStickUp; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = this.dalamud.ClientState.GamepadState.LeftStickDown; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L1); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.R1); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L2); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.R2); + } } private void Display() From 5d57c3f687da6de0cf0deba6d1fef53dee2f20b2 Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Sun, 18 Apr 2021 15:51:52 +0200 Subject: [PATCH 3/9] fix: Spelling mistake and forgotten conditional compiling --- Dalamud/Interface/InterfaceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index 0b67f9a63..dbf1556c8 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -478,7 +478,7 @@ namespace Dalamud.Interface var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0; // NOTE (Chiv) Activate ImGui navigation via L1+L3 press - // (mimicking to how mouse navigation is activated via L1+R3 press in game). + // (mimicking how mouse navigation is activated via L1+R3 press in game). if (gamepadEnabled && this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L1) > 0 && this.dalamud.ClientState.GamepadState.Pressed(GamepadButtons.L3) > 0) From 9a9aae3db73abf782c0917ec881d9048fb867e7d Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Wed, 28 Apr 2021 09:45:29 +0200 Subject: [PATCH 4/9] chore: Rebase on current master and re-add changes to conflicting files --- Dalamud/Interface/DalamudDataWindow.cs | 56 +++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/DalamudDataWindow.cs b/Dalamud/Interface/DalamudDataWindow.cs index 5285a3a74..460132394 100644 --- a/Dalamud/Interface/DalamudDataWindow.cs +++ b/Dalamud/Interface/DalamudDataWindow.cs @@ -36,7 +36,7 @@ namespace Dalamud.Interface private string[] dataKinds = new[] { "ServerOpCode", "Address", "Actor Table", "Font Test", "Party List", "Plugin IPC", "Condition", - "Gauge", "Command", "Addon", "Addon Inspector", "StartInfo", "Target", "Toast", "ImGui", "Tex", + "Gauge", "Command", "Addon", "Addon Inspector", "StartInfo", "Target", "Toast", "ImGui", "Tex", "Gamepad", }; private bool drawActors = false; @@ -391,6 +391,60 @@ namespace Dalamud.Interface } break; + + // Gamepad + case 16: + Action> helper = (text, mask, resolve) => + { + ImGui.Text($"{text} {mask:X4}"); + ImGui.Text($"DPadLeft {resolve(GamepadButtons.DpadLeft)} " + + $"DPadUp {resolve(GamepadButtons.DpadUp)} " + + $"DPadRight {resolve(GamepadButtons.DpadRight)} " + + $"DPadDown {resolve(GamepadButtons.DpadDown)} "); + ImGui.Text($"West {resolve(GamepadButtons.West)} " + + $"North {resolve(GamepadButtons.North)} " + + $"East {resolve(GamepadButtons.East)} " + + $"South {resolve(GamepadButtons.South)} "); + ImGui.Text($"L1 {resolve(GamepadButtons.L1)} " + + $"L2 {resolve(GamepadButtons.L2)} " + + $"R1 {resolve(GamepadButtons.R1)} " + + $"R2 {resolve(GamepadButtons.R2)} "); + ImGui.Text($"Select {resolve(GamepadButtons.Select)} " + + $"Start {resolve(GamepadButtons.Start)} " + + $"L3 {resolve(GamepadButtons.L3)} " + + $"R3 {resolve(GamepadButtons.R3)} "); + }; +#if DEBUG + ImGui.Text($"GamepadInput {this.dalamud.ClientState.GamepadState.GamepadInput.ToString("X")}"); + if (ImGui.IsItemHovered()) ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{this.dalamud.ClientState.GamepadState.GamepadInput.ToString("X")}"); +#endif + + helper( + "Buttons Raw", + this.dalamud.ClientState.GamepadState.ButtonsRaw, + this.dalamud.ClientState.GamepadState.Raw); + helper( + "Buttons Pressed", + this.dalamud.ClientState.GamepadState.ButtonsPressed, + this.dalamud.ClientState.GamepadState.Pressed); + helper( + "Buttons Repeat", + this.dalamud.ClientState.GamepadState.ButtonsRepeat, + this.dalamud.ClientState.GamepadState.Repeat); + helper( + "Buttons Released", + this.dalamud.ClientState.GamepadState.ButtonsReleased, + this.dalamud.ClientState.GamepadState.Released); + ImGui.Text($"LeftStickLeft {this.dalamud.ClientState.GamepadState.LeftStickLeft:0.00} " + + $"LeftStickUp {this.dalamud.ClientState.GamepadState.LeftStickUp:0.00} " + + $"LeftStickRight {this.dalamud.ClientState.GamepadState.LeftStickRight:0.00} " + + $"LeftStickDown {this.dalamud.ClientState.GamepadState.LeftStickDown:0.00} "); + ImGui.Text($"RightStickLeft {this.dalamud.ClientState.GamepadState.RightStickLeft:0.00} " + + $"RightStickUp {this.dalamud.ClientState.GamepadState.RightStickUp:0.00} " + + $"RightStickRight {this.dalamud.ClientState.GamepadState.RightStickRight:0.00} " + + $"RightStickDown {this.dalamud.ClientState.GamepadState.RightStickDown:0.00} "); + break; } } else From e4521c0100efdc97e04dff70f5f287c699c77fa1 Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Sat, 1 May 2021 13:43:48 +0200 Subject: [PATCH 5/9] fix: Race condition between GamepadPoll detour and ImGui setup Ideally, we would use (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0 for testing in the GamepadPollDetour whether ImGui should handle gamepad controls or not. However, this has a race condition during load with the detour which sets up ImGui and throws if our detour gets called before the other, so we opt-in for a dedicated boolean. --- Dalamud/Game/ClientState/GamepadState.cs | 119 ++++++++++++++--------- Dalamud/Interface/InterfaceManager.cs | 5 +- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/Dalamud/Game/ClientState/GamepadState.cs b/Dalamud/Game/ClientState/GamepadState.cs index 0c33f28bf..7e04a7a71 100644 --- a/Dalamud/Game/ClientState/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamepadState.cs @@ -3,6 +3,7 @@ using Dalamud.Game.ClientState.Structs; using Dalamud.Hooking; using ImGuiNET; +using Serilog; namespace Dalamud.Game.ClientState { @@ -28,6 +29,9 @@ namespace Dalamud.Game.ClientState /// Resolver knowing the pointer to the GamepadPoll function. public GamepadState(ClientStateAddressResolver resolver) { +#if DEBUG + Log.Verbose("GamepadPoll address {GamepadPoll}", resolver.GamepadPoll); +#endif this.gamepadPoll = new Hook( resolver.GamepadPoll, (ControllerPoll)this.GamepadPollDetour); @@ -118,10 +122,21 @@ namespace Dalamud.Game.ClientState /// internal ushort ButtonsRepeat { get; private set; } + /// + /// Gets or sets a value indicating whether detour should block gamepad input for game. + /// + /// Ideally, we would use + /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0 + /// but this has a race condition during load with the detour which sets up ImGui + /// and throws if our detour gets called before the other. + /// + internal bool NavEnableGamepad { get; set; } + /// /// Gets whether has been pressed. /// /// Only true on first frame of the press. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. /// /// The button to check for. /// 1 if pressed, 0 otherwise. @@ -131,6 +146,7 @@ namespace Dalamud.Game.ClientState /// Gets whether is being pressed. /// /// True in intervals if button is held down. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. /// /// The button to check for. /// 1 if still pressed during interval, 0 otherwise or in between intervals. @@ -140,6 +156,7 @@ namespace Dalamud.Game.ClientState /// Gets whether has been released. /// /// Only true the frame after release. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. /// /// The button to check for. /// 1 if released, 0 otherwise. @@ -173,56 +190,68 @@ namespace Dalamud.Game.ClientState private int GamepadPollDetour(IntPtr gamepadInput) { -#if DEBUG - this.GamepadInput = gamepadInput; -#endif var original = this.gamepadPoll.Original(gamepadInput); - var input = (GamepadInput*)gamepadInput; - this.leftStickX = input->LeftStickX; - this.leftStickY = input->LeftStickY; - this.rightStickX = input->RightStickX; - this.rightStickY = input->RightStickY; - this.ButtonsRaw = input->ButtonsRaw; - this.ButtonsPressed = input->ButtonsPressed; - this.ButtonsReleased = input->ButtonsReleased; - this.ButtonsRepeat = input->ButtonsRepeat; - - if ((ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) + try { - input->LeftStickX = 0; - input->LeftStickY = 0; - input->RightStickX = 0; - input->RightStickY = 0; +#if DEBUG + this.GamepadInput = gamepadInput; +#endif + var input = (GamepadInput*)gamepadInput; + this.leftStickX = input->LeftStickX; + this.leftStickY = input->LeftStickY; + this.rightStickX = input->RightStickX; + this.rightStickY = input->RightStickY; + this.ButtonsRaw = input->ButtonsRaw; + this.ButtonsPressed = input->ButtonsPressed; + this.ButtonsReleased = input->ButtonsReleased; + this.ButtonsRepeat = input->ButtonsRepeat; - // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` - // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). - // It does block, however, all input to the game. - // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2 - // and the digipad (in some situations, but thankfully not in menus) functional. - // We can either: - // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or - // (b) ignore it as so far it seems only a 'visual' error - // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input, - // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them - // because of the other blocked input) - // `ButtonPressed` is pretty useful so we opt-in to (b). - // This is debatable. - // ImGui itself does not care either way as it uses the Raw values and does its own state handling. - // input->ButtonsRaw &= (ushort)~GamepadButtons.L2; - // input->ButtonsRaw &= (ushort)~GamepadButtons.R2; - // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadDown; - // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadLeft; - // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadUp; - // input->ButtonsRaw &= (ushort)~GamepadButtons.DpadRight; - input->ButtonsPressed = 0; - input->ButtonsReleased = 0; - input->ButtonsRepeat = 0; - return 0; + if (this.NavEnableGamepad) + { + input->LeftStickX = 0; + input->LeftStickY = 0; + input->RightStickX = 0; + input->RightStickY = 0; + + // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` + // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). + // It does block, however, all input to the game. + // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2 + // and the digipad (in some situations, but thankfully not in menus) functional. + // We can either: + // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or + // (b) ignore it as so far it seems only a 'visual' error + // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input, + // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them + // because of the other blocked input) + // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on + // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. + // This is debatable. + // ImGui itself does not care either way as it uses the Raw values and does its own state handling. + input->ButtonsRaw &= (ushort)~GamepadButtons.L2; + input->ButtonsRaw &= (ushort)~GamepadButtons.R2; + input->ButtonsRaw &= (ushort)~GamepadButtons.DpadDown; + input->ButtonsRaw &= (ushort)~GamepadButtons.DpadLeft; + input->ButtonsRaw &= (ushort)~GamepadButtons.DpadUp; + input->ButtonsRaw &= (ushort)~GamepadButtons.DpadRight; + input->ButtonsPressed = 0; + input->ButtonsReleased = 0; + input->ButtonsRepeat = 0; + return 0; + } + + // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the + // original, zero or do the work adjusting the bits. + return original; } + catch (Exception e) + { + Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!"); - // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the - // original, zero or do the work adjusting the bits. - return original; + // NOTE (Chiv) Explicitly deactivate on error + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; + return original; + } } private void Dispose(bool disposing) diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index dbf1556c8..3209fe9d9 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -304,6 +304,9 @@ namespace Dalamud.Interface ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; } + // NOTE (Chiv) Explicitly deactivate on dalamud boot + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; + ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); Log.Information("[IM] Scene & ImGui setup OK!"); @@ -484,7 +487,7 @@ namespace Dalamud.Interface && this.dalamud.ClientState.GamepadState.Pressed(GamepadButtons.L3) > 0) { ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad; - this.dalamud.DalamudUi.TogglePluginInstaller(); + this.dalamud.ClientState.GamepadState.NavEnableGamepad ^= true; } if (gamepadEnabled From 2a2419883b63762acc7a686581437d0ce802484e Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Sat, 1 May 2021 13:45:56 +0200 Subject: [PATCH 6/9] feat: Instead of toggling PluginInstaller on mode change, toggle via R3. --- Dalamud/Interface/DalamudSettingsWindow.cs | 4 ++-- Dalamud/Interface/InterfaceManager.cs | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Dalamud/Interface/DalamudSettingsWindow.cs b/Dalamud/Interface/DalamudSettingsWindow.cs index c350e9e13..8cc68ec80 100644 --- a/Dalamud/Interface/DalamudSettingsWindow.cs +++ b/Dalamud/Interface/DalamudSettingsWindow.cs @@ -23,7 +23,7 @@ namespace Dalamud.Interface { this.dalamud = dalamud; - this.Size = new Vector2(740, 525); + this.Size = new Vector2(740, 550); this.SizeCondition = ImGuiCond.FirstUseEver; this.dalamudMessagesChatType = this.dalamud.Configuration.GeneralChatType; @@ -231,7 +231,7 @@ namespace Dalamud.Interface ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows.")); ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Enable navigation of ImGui windows via gamepad."), ref this.doGamepad); - ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and ImGui navigation via L1+L3.")); + ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and ImGui navigation via L1+L3.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled.")); ImGui.EndTabItem(); } diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index 3209fe9d9..604c95266 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -509,6 +509,11 @@ namespace Dalamud.Interface ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.R1); ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.L2); ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = this.dalamud.ClientState.GamepadState.Raw(GamepadButtons.R2); + + if (this.dalamud.ClientState.GamepadState.Pressed(GamepadButtons.R3) > 0) + { + this.dalamud.DalamudUi.TogglePluginInstaller(); + } } } From 66d42a7cfd0ded8d7571886768d353a1134947aa Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Sun, 2 May 2021 11:43:31 +0200 Subject: [PATCH 7/9] feat: Draw a main-viewport-big notifier on screen whenever in gamepad mode This notifier is a light grey-ish background and some text in the upper left corner of the main viewport. --- Dalamud/Interface/DalamudInterface.cs | 12 ++++++ .../Interface/GamepadModeNotifierWindow.cs | 41 +++++++++++++++++++ Dalamud/Interface/InterfaceManager.cs | 1 + 3 files changed, 54 insertions(+) create mode 100644 Dalamud/Interface/GamepadModeNotifierWindow.cs diff --git a/Dalamud/Interface/DalamudInterface.cs b/Dalamud/Interface/DalamudInterface.cs index 9283ff79e..e7d330b55 100644 --- a/Dalamud/Interface/DalamudInterface.cs +++ b/Dalamud/Interface/DalamudInterface.cs @@ -35,6 +35,7 @@ namespace Dalamud.Interface private readonly ComponentDemoWindow componentDemoWindow; private readonly ColorDemoWindow colorDemoWindow; private readonly ScratchpadWindow scratchpadWindow; + private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; private readonly WindowSystem windowSystem = new WindowSystem("DalamudCore"); @@ -116,6 +117,9 @@ namespace Dalamud.Interface }; this.windowSystem.AddWindow(this.scratchpadWindow); + this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow(); + this.windowSystem.AddWindow(this.gamepadModeNotifierWindow); + Log.Information("[DUI] Windows added"); if (dalamud.Configuration.LogOpenAtStartup) @@ -553,5 +557,13 @@ namespace Dalamud.Interface { this.scratchpadWindow.IsOpen ^= true; } + + /// + /// Toggle the gamepad notifier window window. + /// + internal void ToggleGamePadNotifierWindow() + { + this.gamepadModeNotifierWindow.IsOpen ^= true; + } } } diff --git a/Dalamud/Interface/GamepadModeNotifierWindow.cs b/Dalamud/Interface/GamepadModeNotifierWindow.cs new file mode 100644 index 000000000..b3cbef108 --- /dev/null +++ b/Dalamud/Interface/GamepadModeNotifierWindow.cs @@ -0,0 +1,41 @@ +using System.Numerics; + +using Dalamud.Interface.Windowing; +using ImGuiNET; + +namespace Dalamud.Interface +{ + /// + /// Class responsible for drawing a notifier on screen that gamepad mode is active. + /// + internal class GamepadModeNotifierWindow : Window + { + /// + /// Initializes a new instance of the class. + /// + public GamepadModeNotifierWindow() + : base( + "###DalamudGamepadModeNotifier", + ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs + | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav + | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings, + true) + { + this.Size = Vector2.Zero; + this.SizeCondition = ImGuiCond.Always; + this.IsOpen = false; + } + + /// + /// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode. + /// + public override void Draw() + { + var drawList = ImGui.GetBackgroundDrawList(); + drawList.PushClipRectFullScreen(); + drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A); + drawList.AddText(Vector2.One, 0xFFFFFFFF, "Gamepad mode is ON. Press R1+L3 to deactivate, press R3 to toggle PluginInstaller."); + drawList.PopClipRect(); + } + } +} diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index 604c95266..4d7f8ecc4 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -488,6 +488,7 @@ namespace Dalamud.Interface { ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad; this.dalamud.ClientState.GamepadState.NavEnableGamepad ^= true; + this.dalamud.DalamudUi.ToggleGamePadNotifierWindow(); } if (gamepadEnabled From d65f2c6cd98213e078011d0c66dbe3f46745f5a0 Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Sun, 2 May 2021 12:21:49 +0200 Subject: [PATCH 8/9] refactor: Use a constant for the gamepad button deletion mask Add Flags-Attribute to GamepadButtons enums. --- Dalamud/Game/ClientState/GamepadButtons.cs | 10 +++++++++- Dalamud/Game/ClientState/GamepadState.cs | 13 +++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/ClientState/GamepadButtons.cs b/Dalamud/Game/ClientState/GamepadButtons.cs index e1db685e0..80a745d53 100644 --- a/Dalamud/Game/ClientState/GamepadButtons.cs +++ b/Dalamud/Game/ClientState/GamepadButtons.cs @@ -1,10 +1,18 @@ -namespace Dalamud.Game.ClientState +using System; + +namespace Dalamud.Game.ClientState { /// /// Bitmask of the Button ushort used by the game. /// + [Flags] public enum GamepadButtons : ushort { + /// + /// No buttons pressed. + /// + None = 0, + /// /// Digipad up. /// diff --git a/Dalamud/Game/ClientState/GamepadState.cs b/Dalamud/Game/ClientState/GamepadState.cs index 7e04a7a71..b9711d093 100644 --- a/Dalamud/Game/ClientState/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamepadState.cs @@ -228,12 +228,13 @@ namespace Dalamud.Game.ClientState // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. // This is debatable. // ImGui itself does not care either way as it uses the Raw values and does its own state handling. - input->ButtonsRaw &= (ushort)~GamepadButtons.L2; - input->ButtonsRaw &= (ushort)~GamepadButtons.R2; - input->ButtonsRaw &= (ushort)~GamepadButtons.DpadDown; - input->ButtonsRaw &= (ushort)~GamepadButtons.DpadLeft; - input->ButtonsRaw &= (ushort)~GamepadButtons.DpadUp; - input->ButtonsRaw &= (ushort)~GamepadButtons.DpadRight; + const ushort deletionMask = (ushort)(~GamepadButtons.L2 + & ~GamepadButtons.R2 + & ~GamepadButtons.DpadDown + & ~GamepadButtons.DpadLeft + & ~GamepadButtons.DpadUp + & ~GamepadButtons.DpadRight); + input->ButtonsRaw &= deletionMask; input->ButtonsPressed = 0; input->ButtonsReleased = 0; input->ButtonsRepeat = 0; From ab34b584f8f54f14f1bcb1abead885c5124ab750 Mon Sep 17 00:00:00 2001 From: Chivalrik Date: Sun, 2 May 2021 12:33:56 +0200 Subject: [PATCH 9/9] fix: Add localisation to notifier string --- Dalamud/Interface/GamepadModeNotifierWindow.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Dalamud/Interface/GamepadModeNotifierWindow.cs b/Dalamud/Interface/GamepadModeNotifierWindow.cs index b3cbef108..1bcf63e6e 100644 --- a/Dalamud/Interface/GamepadModeNotifierWindow.cs +++ b/Dalamud/Interface/GamepadModeNotifierWindow.cs @@ -1,5 +1,6 @@ using System.Numerics; +using CheapLoc; using Dalamud.Interface.Windowing; using ImGuiNET; @@ -34,7 +35,12 @@ namespace Dalamud.Interface var drawList = ImGui.GetBackgroundDrawList(); drawList.PushClipRectFullScreen(); drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A); - drawList.AddText(Vector2.One, 0xFFFFFFFF, "Gamepad mode is ON. Press R1+L3 to deactivate, press R3 to toggle PluginInstaller."); + drawList.AddText( + Vector2.One, + 0xFFFFFFFF, + Loc.Localize( + "DalamudGamepadModeNotifierText", + "Gamepad mode is ON. Press R1+L3 to deactivate, press R3 to toggle PluginInstaller.")); drawList.PopClipRect(); } }