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
This commit is contained in:
Chivalrik 2021-04-16 10:58:37 +02:00
parent 44551d43ae
commit 9d38069fb3
5 changed files with 238 additions and 0 deletions

View file

@ -98,6 +98,8 @@ namespace Dalamud.Game.ClientState
/// </summary>
public KeyState KeyState;
public GamepadState GamepadState;
/// <summary>
/// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
/// </summary>
@ -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;

View file

@ -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,
}
}

View file

@ -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> 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<ControllerPoll>(
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);
}
}
}

View file

@ -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
}
}

View file

@ -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?
}