diff --git a/src/OWML.Common/ControlPathConstants.cs b/src/OWML.Common/ControlPathConstants.cs new file mode 100644 index 00000000..8e4ff0fa --- /dev/null +++ b/src/OWML.Common/ControlPathConstants.cs @@ -0,0 +1,212 @@ +namespace OWML.Common +{ + public static class ControlPathConstants + { + public const char PATH_SEPARATOR_CHAR = '/'; + public const string PATH_SEPARATOR = "/"; + + // common axes / directions + public const string X = "x"; + public const string Y = "y"; + + public const string UP = "up"; + public const string DOWN = "down"; + public const string UP_CAP = "Up"; + public const string DOWN_CAP = "Down"; + public const string LEFT = "left"; + public const string RIGHT = "right"; + + public const string MIDDLE = "middle"; + public const string BACK = "back"; + public const string FORWARD = "forward"; + + public const string NORTH = "North"; + public const string SOUTH = "South"; + public const string EAST = "East"; + public const string WEST = "West"; + + // misc + public const string BUTTON = "button"; + public const string BUTTON_CAP = "Button"; + + public const string NONE = "none"; + + public static class Keyboard + { + public const string DEVICE = ""; + + // basic keys + public const string SPACE = "space"; + public const string ENTER = "enter"; + public const string TAB = "tab"; + + public const string BACKQUOTE = "backquote"; + public const string QUOTE = "quote"; + public const string SEMICOLON = "semicolon"; + public const string COMMA = "comma"; + public const string PERIOD = "period"; + public const string SLASH = "slash"; + public const string BACKSLASH = "backslash"; + + public const string MINUS = "minus"; + public const string EQUALS = "equals"; + + // modifiers + public const string SHIFT = "Shift"; + public const string ALT = "Alt"; + public const string CTRL = "Ctrl"; + public const string META = "Meta"; + public const string BRACKET = "Bracket"; + + public const string LEFT_SHIFT = LEFT + SHIFT; + public const string RIGHT_SHIFT = RIGHT + SHIFT; + public const string LEFT_ALT = LEFT + ALT; + public const string RIGHT_ALT = RIGHT + ALT; + public const string LEFT_CTRL = LEFT + CTRL; + public const string RIGHT_CTRL = RIGHT + CTRL; + public const string LEFT_META = LEFT + META; + public const string RIGHT_META = RIGHT + META; + public const string LEFT_BRACKET = LEFT + BRACKET; + public const string RIGHT_BRACKET = RIGHT + BRACKET; + + // navigation + public const string ARROW = "Arrow"; + public const string LEFT_ARROW = LEFT + ARROW; + public const string RIGHT_ARROW = RIGHT + ARROW; + public const string UP_ARROW = UP + ARROW; + public const string DOWN_ARROW = DOWN + ARROW; + + public const string ESCAPE = "escape"; + public const string BACKSPACE = "backspace"; + + public const string PAGE = "page"; + public const string PAGE_UP = PAGE + UP_CAP; + public const string PAGE_DOWN = PAGE + DOWN_CAP; + + public const string HOME = "home"; + public const string END = "end"; + public const string INSERT = "insert"; + public const string DELETE = "delete"; + + // locks + public const string LOCK = "Lock"; + public const string CAPS_LOCK = "caps" + LOCK; + public const string NUM_LOCK = "num" + LOCK; + public const string SCROLL_LOCK = "scroll" + LOCK; + + // numpad + public const string NUMPAD = "numpad"; + public const string NUMPAD_ENTER = NUMPAD + "Enter"; + public const string NUMPAD_DIVIDE = NUMPAD + "Divide"; + public const string NUMPAD_MULTIPLY = NUMPAD + "Multiply"; + public const string NUMPAD_PLUS = NUMPAD + "Plus"; + public const string NUMPAD_MINUS = NUMPAD + "Minus"; + public const string NUMPAD_PERIOD = NUMPAD + "Period"; + public const string NUMPAD_EQUALS = NUMPAD + "Equals"; + + // misc + public const string PRINT_SCREEN = "printScreen"; + public const string PAUSE = "pause"; + public const string CONTEXT_MENU = "contextMenu"; + public const string OEM = "OEM"; + public const string F = "f"; + } + + public static class Mouse + { + public const string DEVICE = ""; + + // buttons + public const string LEFT_BUTTON = LEFT + BUTTON_CAP; + public const string RIGHT_BUTTON = RIGHT + BUTTON_CAP; + public const string MIDDLE_BUTTON = MIDDLE + BUTTON_CAP; + public const string BACK_BUTTON = BACK + BUTTON_CAP; + public const string FORWARD_BUTTON = FORWARD + BUTTON_CAP; + + // delta + public const string DELTA = "delta"; + public const string DELTA_X = DELTA + PATH_SEPARATOR + X; + public const string DELTA_Y = DELTA + PATH_SEPARATOR + Y; + + public const string DELTA_UP = DELTA + PATH_SEPARATOR + UP; + public const string DELTA_DOWN = DELTA + PATH_SEPARATOR + DOWN; + public const string DELTA_LEFT = DELTA + PATH_SEPARATOR + LEFT; + public const string DELTA_RIGHT = DELTA + PATH_SEPARATOR + RIGHT; + + // scroll + public const string SCROLL = "scroll"; + public const string SCROLL_X = SCROLL + PATH_SEPARATOR + X; + public const string SCROLL_Y = SCROLL + PATH_SEPARATOR + Y; + + public const string SCROLL_UP = SCROLL + PATH_SEPARATOR + UP; + public const string SCROLL_DOWN = SCROLL + PATH_SEPARATOR + DOWN; + public const string SCROLL_LEFT = SCROLL + PATH_SEPARATOR + LEFT; + public const string SCROLL_RIGHT = SCROLL + PATH_SEPARATOR + RIGHT; + + // position + public const string POSITION = "position"; + public const string POSITION_X = POSITION + PATH_SEPARATOR + X; + public const string POSITION_Y = POSITION + PATH_SEPARATOR + Y; + } + + public static class Gamepad + { + public const string DEVICE = ""; + + // dpad + public const string DPAD = "dpad"; + public const string DPAD_X = DPAD + PATH_SEPARATOR + X; + public const string DPAD_Y = DPAD + PATH_SEPARATOR + Y; + public const string DPAD_UP = DPAD + PATH_SEPARATOR + UP; + public const string DPAD_DOWN = DPAD + PATH_SEPARATOR + DOWN; + public const string DPAD_LEFT = DPAD + PATH_SEPARATOR + LEFT; + public const string DPAD_RIGHT = DPAD + PATH_SEPARATOR + RIGHT; + + // face buttons + public const string BUTTON_NORTH = BUTTON + NORTH; + public const string BUTTON_SOUTH = BUTTON + SOUTH; + public const string BUTTON_EAST = BUTTON + EAST; + public const string BUTTON_WEST = BUTTON + WEST; + + // sticks + public const string STICK = "Stick"; + public const string PRESS = "Press"; + public const string LEFT_STICK = LEFT + STICK; + public const string RIGHT_STICK = RIGHT + STICK; + + // left stick + public const string LEFT_STICK_X = LEFT_STICK + PATH_SEPARATOR + X; + public const string LEFT_STICK_Y = LEFT_STICK + PATH_SEPARATOR + Y; + public const string LEFT_STICK_UP = LEFT_STICK + PATH_SEPARATOR + UP; + public const string LEFT_STICK_DOWN = LEFT_STICK + PATH_SEPARATOR + DOWN; + public const string LEFT_STICK_LEFT = LEFT_STICK + PATH_SEPARATOR + LEFT; + public const string LEFT_STICK_RIGHT = LEFT_STICK + PATH_SEPARATOR + RIGHT; + public const string LEFT_STICK_PRESS = LEFT_STICK + PRESS; + + // right stick + public const string RIGHT_STICK_X = RIGHT_STICK + PATH_SEPARATOR + X; + public const string RIGHT_STICK_Y = RIGHT_STICK + PATH_SEPARATOR + Y; + public const string RIGHT_STICK_UP = RIGHT_STICK + PATH_SEPARATOR + UP; + public const string RIGHT_STICK_DOWN = RIGHT_STICK + PATH_SEPARATOR + DOWN; + public const string RIGHT_STICK_LEFT = RIGHT_STICK + PATH_SEPARATOR + LEFT; + public const string RIGHT_STICK_RIGHT = RIGHT_STICK + PATH_SEPARATOR + RIGHT; + public const string RIGHT_STICK_PRESS = RIGHT_STICK + PRESS; + + // shoulders + public const string SHOULDER = "Shoulder"; + public const string LEFT_SHOULDER = LEFT + SHOULDER; + public const string RIGHT_SHOULDER = RIGHT + SHOULDER; + + // triggers + public const string TRIGGER = "Trigger"; + public const string LEFT_TRIGGER = LEFT + TRIGGER; + public const string RIGHT_TRIGGER = RIGHT + TRIGGER; + + // misc + public const string START = "start"; + public const string SELECT = "select"; + public const string SHARE = "share"; + public const string SYSTEM_BUTTON = "system" + BUTTON_CAP; + } + } +} diff --git a/src/OWML.Common/Enums/GamepadBinding.cs b/src/OWML.Common/Enums/GamepadBinding.cs index 43f3e441..62313907 100644 --- a/src/OWML.Common/Enums/GamepadBinding.cs +++ b/src/OWML.Common/Enums/GamepadBinding.cs @@ -2,7 +2,9 @@ { public enum GamepadBinding { - DPadUp, + None = -1, + + DPadUp = 0, DPadDown, DPadLeft, DPadRight, @@ -29,6 +31,7 @@ public enum GamepadBinding LeftShoulder, RightShoulder, + /// /// Xbox: Menu Button, PS4: Options Button /// @@ -37,21 +40,30 @@ public enum GamepadBinding /// Xbox: View Button, PS4: Touchpad Press /// Select, + LeftTrigger, RightTrigger, + /// - /// Xbox: ?, PS4: Share Button + /// Xbox: Unused, PS4: Share Button /// + /// + /// Even though the Share button is present on newer Xbox controllers, it doesn't have any functionality in Unity. It is only used as a capture/record button for Game Bar. + /// Share, /// - /// Xbox: ?, PS4: PlayStation Button + /// Xbox: Unused, PS4: PlayStation Button /// + /// + /// Even though the System button is present on all Xbox controllers, it doesn't have any functionality in Unity. It is only used to open Game Bar and/or Steam overlay. + /// SystemButton, LeftStickLeft, LeftStickRight, LeftStickUp, LeftStickDown, + RightStickLeft, RightStickRight, RightStickUp, diff --git a/src/OWML.Common/Enums/MouseBinding.cs b/src/OWML.Common/Enums/MouseBinding.cs new file mode 100644 index 00000000..c7b661b0 --- /dev/null +++ b/src/OWML.Common/Enums/MouseBinding.cs @@ -0,0 +1,24 @@ +namespace OWML.Common.Enums +{ + public enum MouseBinding + { + None = -1, + + Left = 0, + Right, + Middle, + + Back, + Forward, + + DeltaUp, + DeltaDown, + DeltaLeft, + DeltaRight, + + ScrollUp, + ScrollDown, + ScrollLeft, + ScrollRight + } +} diff --git a/src/OWML.Common/IRebindingHelper.cs b/src/OWML.Common/IRebindingHelper.cs index 2651d4d0..60a2b920 100644 --- a/src/OWML.Common/IRebindingHelper.cs +++ b/src/OWML.Common/IRebindingHelper.cs @@ -27,6 +27,24 @@ public InputConsts.InputCommandType RegisterRebindable( bool axis, float pressedThreshold = 0.4f); + /// + /// Register a key rebind. + /// + /// The name of the input. Showed in the mod config menu. Must be unique in your mod. + /// The tooltip showed in the mod config menu. + /// The default mouse binding. + /// The default gamepad binding. + /// Should be true for inputs that expect an analog input - eg triggers. For simple buttons, this should be false. See the docs for more info. + /// Used to control when returns true - see docs for more info. + /// An used in or . + public InputConsts.InputCommandType RegisterRebindable( + string name, + string tooltip, + MouseBinding mouseKeybind, + GamepadBinding gamepadKeybind, + bool axis, + float pressedThreshold = 0.4f); + /// /// Register a key rebind with a positive and negative action. /// @@ -49,6 +67,28 @@ public InputConsts.InputCommandType RegisterRebindable( bool axis, float pressedThreshold = 0.4f); + /// + /// Register a key rebind with a positive and negative action. + /// + /// The name of the input. Showed in the mod config menu. Must be unique in your mod. + /// The tooltip showed in the mod config menu. + /// The default mouse binding for the positive (ie. right, forward) action. + /// The default gamepad binding for the positive (ie. right, up) action. + /// The default mouse binding for the negative (ie. left, back) action. + /// The default gamepad binding for the negative (ie. left, down) action. + /// Should be true for inputs that expect an analog input - eg triggers. For simple buttons, this should be false. See the docs for more info. + /// Used to control when returns true - see docs for more info. + /// An used in or . + public InputConsts.InputCommandType RegisterRebindable( + string name, + string tooltip, + MouseBinding positiveMouseKeybind, + GamepadBinding positiveGamepadKeybind, + MouseBinding negativeMouseKeybind, + GamepadBinding negativeGamepadKeybind, + bool axis, + float pressedThreshold = 0.4f); + /// /// Register a 2D input from two 1D inputs. /// @@ -60,5 +100,19 @@ public InputConsts.InputCommandType RegisterComposite( string name, InputConsts.InputCommandType xAxis, InputConsts.InputCommandType yAxis); + + /// + /// Mark a rebindable so the menu system will flip its positive/negative + /// binding images (used for horizontal/X axes). + /// + /// The rebindable ID to mark. + public void FlipImages(RebindableID id); + + /// + /// Mark a rebindable so the menu system will flip its positive/negative + /// binding images (used for horizontal/X axes). + /// + /// The command type to mark. + public void FlipImages(InputConsts.InputCommandType xAxis); } } \ No newline at end of file diff --git a/src/OWML.ModHelper.Input/OWMLRebinding.cs b/src/OWML.ModHelper.Input/OWMLRebinding.cs index 66f74120..603d4307 100644 --- a/src/OWML.ModHelper.Input/OWMLRebinding.cs +++ b/src/OWML.ModHelper.Input/OWMLRebinding.cs @@ -8,10 +8,12 @@ namespace OWML.ModHelper.Input public class OWMLRebinding : IOWMLRebinding { public static Dictionary CustomActionMaps = new(); + public static List XAxisRebindables = new(); public OWMLRebinding(IModConsole console, IHarmonyHelper harmony) { - harmony.AddPrefix(typeof(InputCommandManager).GetMethod("LoadActions", [typeof(string)]), typeof(Patches), nameof(Patches.LoadActions)); + harmony.AddPrefix(typeof(InputCommandManager).GetMethod(nameof(InputCommandManager.LoadActions), [typeof(string)]), typeof(Patches), nameof(Patches.LoadActions)); + harmony.AddPrefix(typeof(RebindableLookup).GetMethod(nameof(RebindableLookup.IsXAxisRebindable), [typeof(RebindableID)]), typeof(Patches), nameof(Patches.IsXAxisRebindable)); } } } \ No newline at end of file diff --git a/src/OWML.ModHelper.Input/Patches.cs b/src/OWML.ModHelper.Input/Patches.cs index 936f19b0..605fd4dc 100644 --- a/src/OWML.ModHelper.Input/Patches.cs +++ b/src/OWML.ModHelper.Input/Patches.cs @@ -1,6 +1,9 @@ using System; using UnityEngine; using UnityEngine.InputSystem; +using HarmonyLib; +using OWML.Common; +using System.Linq; namespace OWML.ModHelper.Input { @@ -45,8 +48,8 @@ public static bool LoadActions(InputCommandManager __instance, string json, ref { existingActionMap.AddBinding( binding.path, - action, - binding.groups); + action, + groups: binding.groups); } } } @@ -63,5 +66,18 @@ public static bool LoadActions(InputCommandManager __instance, string json, ref __result = flag; return false; } + + public static bool IsXAxisRebindable(RebindableID id, ref bool __result) + { + if (OWMLRebinding.XAxisRebindables.Contains(id)) + { + __result = true; + return false; + } + else + { + return true; + } + } } } \ No newline at end of file diff --git a/src/OWML.ModHelper.Input/RebindingHelper.cs b/src/OWML.ModHelper.Input/RebindingHelper.cs index 8fe89580..f5bb73b0 100644 --- a/src/OWML.ModHelper.Input/RebindingHelper.cs +++ b/src/OWML.ModHelper.Input/RebindingHelper.cs @@ -23,15 +23,27 @@ private string CreateActionAndBinding( string name, bool positive, bool axis, - Key keyboardKeybind, + KeyOrMouse kbmKeybind, GamepadBinding gamepadKeybind) { var positiveName = name + (positive ? "Positive" : "Negative"); - var positiveAction = AddAction(_manifest, positiveName, axis); - AddBinding(_manifest, positiveAction, Keyboard.current[keyboardKeybind].path, InputConsts.InputControlSchemes.KEYBOARDMOUSE); - AddBinding(_manifest, positiveAction, GetGamepadPath(gamepadKeybind), InputConsts.InputControlSchemes.GAMEPAD); - return positiveName; + return CreateActionAndBinding(_manifest, positiveName, axis, kbmKeybind, gamepadKeybind); + } + + private string CreateActionAndBinding( + IModManifest manifest, + string name, + bool axis, + KeyOrMouse kbmKeybind, + GamepadBinding gamepadKeybind) + { + var action = AddAction(_manifest, name, axis); + + AddBinding(_manifest, action, kbmKeybind.GetInputCommandPath(), InputConsts.InputControlSchemes.KEYBOARDMOUSE); + AddBinding(_manifest, action, gamepadKeybind.GetInputCommandPath(), InputConsts.InputControlSchemes.GAMEPAD); + + return name; } public InputConsts.InputCommandType RegisterRebindable( @@ -41,15 +53,53 @@ public InputConsts.InputCommandType RegisterRebindable( GamepadBinding gamepadKeybind, bool axis, float pressedThreshold = 0.4f) + { + return RegisterRebindable( + name, + tooltip, + new KeyOrMouse(keyboardKeybind), + gamepadKeybind, + axis, + pressedThreshold + ); + } + public InputConsts.InputCommandType RegisterRebindable( + string name, + string tooltip, + MouseBinding mouseKeybind, + GamepadBinding gamepadKeybind, + bool axis, + float pressedThreshold = 0.4f) + { + return RegisterRebindable( + name, + tooltip, + new KeyOrMouse(mouseKeybind), + gamepadKeybind, + axis, + pressedThreshold + ); + } + + public InputConsts.InputCommandType RegisterRebindable( + string name, + string tooltip, + KeyOrMouse kbmKeybind, + GamepadBinding gamepadKeybind, + bool axis, + float pressedThreshold = 0.4f) { var uniqueName = _manifest.UniqueName + name; var commandType = EnumUtils.Create(uniqueName); var rebindableId = EnumUtils.Create(uniqueName); var inputCommandData = InputCommandDefinitions.InputCommandData.CreateCommandData(CommandDataType.Axis, commandType); - var action = CreateActionAndBinding(_manifest, uniqueName, true, axis, keyboardKeybind, gamepadKeybind); + + var action = CreateActionAndBinding(_manifest, uniqueName, axis, kbmKeybind, gamepadKeybind); + inputCommandData.TrySetAsAxis(rebindableId, action); InputCommandDefinitions.AddInputCommandData(inputCommandData); + Rebindables.Add((rebindableId, name, tooltip)); ((InputManager)OWInput.SharedInputManager).commandManager.OnInputCommandsInitialized += () => @@ -70,14 +120,60 @@ public InputConsts.InputCommandType RegisterRebindable( GamepadBinding negativeGamepadKeybind, bool axis, float pressedThreshold = 0.4f) + { + return RegisterRebindable( + name, + tooltip, + new KeyOrMouse(positiveKeyboardKeybind), + positiveGamepadKeybind, + new KeyOrMouse(negativeKeyboardKeybind), + negativeGamepadKeybind, + axis, + pressedThreshold + ); + } + + public InputConsts.InputCommandType RegisterRebindable( + string name, + string tooltip, + MouseBinding positiveMouseKeybind, + GamepadBinding positiveGamepadKeybind, + MouseBinding negativeMouseKeybind, + GamepadBinding negativeGamepadKeybind, + bool axis, + float pressedThreshold = 0.4f) + { + return RegisterRebindable( + name, + tooltip, + new KeyOrMouse(positiveMouseKeybind), + positiveGamepadKeybind, + new KeyOrMouse(negativeMouseKeybind), + negativeGamepadKeybind, + axis, + pressedThreshold + ); + } + + public InputConsts.InputCommandType RegisterRebindable( + string name, + string tooltip, + KeyOrMouse positiveKbmKeybind, + GamepadBinding positiveGamepadKeybind, + KeyOrMouse negativeKbmKeybind, + GamepadBinding negativeGamepadKeybind, + bool axis, + float pressedThreshold = 0.4f) { var uniqueName = _manifest.UniqueName + name; var commandType = EnumUtils.Create(uniqueName); var rebindableId = EnumUtils.Create(uniqueName); var inputCommandData = InputCommandDefinitions.InputCommandData.CreateCommandData(CommandDataType.Axis, commandType); - var positiveAction = CreateActionAndBinding(_manifest, uniqueName, true, axis, positiveKeyboardKeybind, positiveGamepadKeybind); - var negativeAction = CreateActionAndBinding(_manifest, uniqueName, false, axis, negativeKeyboardKeybind, negativeGamepadKeybind); + + var positiveAction = CreateActionAndBinding(_manifest, uniqueName, true, axis, positiveKbmKeybind, positiveGamepadKeybind); + var negativeAction = CreateActionAndBinding(_manifest, uniqueName, false, axis, negativeKbmKeybind, negativeGamepadKeybind); + inputCommandData.TrySetAsAxis(rebindableId, positiveAction, negativeAction); InputCommandDefinitions.AddInputCommandData(inputCommandData); @@ -99,11 +195,11 @@ public InputConsts.InputCommandType RegisterComposite( { var uniqueName = _manifest.UniqueName + name; - var yName = EnumUtils.GetName(typeof(InputConsts.InputCommandType), yAxis); - var xName = EnumUtils.GetName(typeof(InputConsts.InputCommandType), xAxis); + var yName = EnumUtils.GetName(yAxis); + var xName = EnumUtils.GetName(xAxis); - var yId = (RebindableID)EnumUtils.Parse(typeof(RebindableID), yName); - var xId = (RebindableID)EnumUtils.Parse(typeof(RebindableID), xName); + var yId = EnumUtils.Parse(yName); + var xId = EnumUtils.Parse(xName); var commandType = EnumUtils.Create(uniqueName); var inputCommandData = InputCommandDefinitions.InputCommandData.CreateCommandData(CommandDataType.Composite, commandType); @@ -115,76 +211,6 @@ public InputConsts.InputCommandType RegisterComposite( return commandType; } - private string GetGamepadPath(GamepadBinding binding) - { - switch (binding) - { - case GamepadBinding.LeftStickUp: - return "/leftStick/up"; - case GamepadBinding.LeftStickDown: - return "/leftStick/down"; - case GamepadBinding.LeftStickLeft: - return "/leftStick/left"; - case GamepadBinding.LeftStickRight: - return "/leftStick/right"; - - case GamepadBinding.RightStickUp: - return "/rightStick/up"; - case GamepadBinding.RightStickDown: - return "/rightStick/down"; - case GamepadBinding.RightStickLeft: - return "/rightStick/left"; - case GamepadBinding.RightStickRight: - return "/rightStick/right"; - - case GamepadBinding.Share: - return "/share"; - case GamepadBinding.SystemButton: - return "/systemButton"; - - case GamepadBinding.DPadUp: - return "/dpad/up"; - case GamepadBinding.DPadDown: - return "/dpad/down"; - case GamepadBinding.DPadLeft: - return "/dpad/left"; - case GamepadBinding.DPadRight: - return "/dpad/right"; - - case GamepadBinding.UpButton: - return "/buttonNorth"; - case GamepadBinding.LeftButton: - return "/buttonEast"; - case GamepadBinding.DownButton: - return "/buttonSouth"; - case GamepadBinding.RightButton: - return "/buttonWest"; - - case GamepadBinding.LeftStickClick: - return "/leftStickPress"; - case GamepadBinding.RightStickClick: - return "/rightStickPress"; - - case GamepadBinding.LeftShoulder: - return "/leftShoulder"; - case GamepadBinding.RightShoulder: - return "/rightShoulder"; - - case GamepadBinding.Start: - return "/start"; - case GamepadBinding.Select: - return "/select"; - - case GamepadBinding.LeftTrigger: - return "/leftTrigger"; - case GamepadBinding.RightTrigger: - return "/rightTrigger"; - - default: - throw new NotImplementedException(); - } - } - private InputAction AddAction(IModManifest manifest, string name, bool axis) { if (!OWMLRebinding.CustomActionMaps.ContainsKey(manifest.UniqueName)) @@ -204,5 +230,39 @@ private void AddBinding(IModManifest manifest, InputAction action, string contro { OWMLRebinding.CustomActionMaps[manifest.UniqueName].AddBinding(control, action, groups: inputControlScheme); } + + public void FlipImages(RebindableID id) + { + OWMLRebinding.XAxisRebindables.Add(id); + } + + private static RebindableID InputCommandToRebindable(InputConsts.InputCommandType inputCommandType) + { + var name = EnumUtils.GetName(inputCommandType); + return EnumUtils.Parse(name); + } + + public void FlipImages(InputConsts.InputCommandType xAxis) + => FlipImages(InputCommandToRebindable(xAxis)); + + public readonly struct KeyOrMouse + { + public readonly Key? key; + public readonly MouseBinding? mouse; + + public KeyOrMouse(Key key) => (this.key, this.mouse) = (key, null); + public KeyOrMouse(MouseBinding mouse) => (this.key, this.mouse) = (null, mouse); + + public string GetInputCommandPath() + { + if (key != null) + return key.Value.GetInputCommandPath(); + + if (mouse != null) + return mouse.Value.GetInputCommandPath(); + + throw new InvalidOperationException(); + } + } } } \ No newline at end of file diff --git a/src/OWML.ModHelper.Menus/ModMenu.cs b/src/OWML.ModHelper.Menus/ModMenu.cs index a2f007b6..b8ba3a2f 100644 --- a/src/OWML.ModHelper.Menus/ModMenu.cs +++ b/src/OWML.ModHelper.Menus/ModMenu.cs @@ -108,7 +108,7 @@ public virtual IModButtonBase AddButton(IModButtonBase button, int index) { var transform = button.Button.transform; var scale = transform.localScale; - transform.parent = Layout.transform; + transform.SetParent(Layout.transform, false); button.Index = index; button.Initialize(this); BaseButtons.Add(button); @@ -185,7 +185,7 @@ private void AddInput(IModInput input, int index) { var transform = input.Element.transform; var scale = transform.localScale; - transform.parent = Layout.transform; + transform.SetParent(Layout.transform, false); input.Index = index; input.Initialize(this); input.Element.transform.localScale = scale; @@ -199,7 +199,7 @@ public IModSeparator AddSeparator(IModSeparator separator, int index) Separators.Add(separator); var transform = separator.Element.transform; var scale = transform.localScale; - transform.parent = Layout.transform; + transform.SetParent(Layout.transform, false); separator.Index = index; separator.Initialize(this); transform.localScale = scale; diff --git a/src/OWML.ModHelper.Menus/ModOptionsMenu.cs b/src/OWML.ModHelper.Menus/ModOptionsMenu.cs index 6bc3fbbd..060deafc 100644 --- a/src/OWML.ModHelper.Menus/ModOptionsMenu.cs +++ b/src/OWML.ModHelper.Menus/ModOptionsMenu.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; -using System.Linq; -using OWML.Common; +using OWML.Common; using OWML.Common.Menus; using OWML.Utils; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; using UnityEngine.UI; namespace OWML.ModHelper.Menus @@ -68,7 +69,7 @@ public void AddTab(IModTabMenu tabMenu, bool enable = true) Menu.SetValue("_menuTabs", tabs); AddSelectablePair(tabMenu); var parent = tabs[0].transform.parent; - tabMenu.TabButton.transform.parent = parent; + tabMenu.TabButton.transform.SetParent(parent, false); tabMenu.OnOpened += () => OnTabOpen(tabMenu); tabMenu.OnClosed += OnTabClose; diff --git a/src/OWML.ModHelper.Menus/NewMenuSystem/MenuManager.cs b/src/OWML.ModHelper.Menus/NewMenuSystem/MenuManager.cs index 5082cd0b..e28024a6 100644 --- a/src/OWML.ModHelper.Menus/NewMenuSystem/MenuManager.cs +++ b/src/OWML.ModHelper.Menus/NewMenuSystem/MenuManager.cs @@ -57,6 +57,8 @@ public MenuManager( OptionsMenuManager = new OptionsMenuManager(console, unityEvents, PopupMenuManager, this); PauseMenuManager = new PauseMenuManager(console); + FixAxisIDCache(); + var harmonyInstance = harmony.GetValue("_harmony"); harmonyInstance.PatchAll(typeof(Patches)); @@ -501,5 +503,25 @@ private SettingType GetSettingType(object setting) _console.WriteLine($"Couldn't work out setting type. Type:{setting.GetType().Name} SettingObjectType:{settingObject?["type"].ToString()}", MessageType.Error); return SettingType.NONE; } + + private static void FixAxisIDCache() + { + // Scroll only has one identifier + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.SCROLL] = AxisIdentifier.KEYBD_MOUSEWHEEL; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.SCROLL_X] = AxisIdentifier.KEYBD_MOUSEWHEEL; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.SCROLL_LEFT] = AxisIdentifier.KEYBD_MOUSEWHEEL; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.SCROLL_RIGHT] = AxisIdentifier.KEYBD_MOUSEWHEEL; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.SCROLL_Y] = AxisIdentifier.KEYBD_MOUSEWHEEL; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.SCROLL_UP] = AxisIdentifier.KEYBD_MOUSEWHEEL; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.SCROLL_DOWN] = AxisIdentifier.KEYBD_MOUSEWHEEL; + + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.DELTA] = AxisIdentifier.KEYBD_MOUSE; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.DELTA_X] = AxisIdentifier.KEYBD_MOUSEX; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.DELTA_LEFT] = AxisIdentifier.KEYBD_MOUSEX; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.DELTA_RIGHT] = AxisIdentifier.KEYBD_MOUSEX; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.DELTA_Y] = AxisIdentifier.KEYBD_MOUSEY; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.DELTA_UP] = AxisIdentifier.KEYBD_MOUSEY; + InputTransitionUtil.AxisIDCache[ControlPathConstants.Mouse.DELTA_DOWN] = AxisIdentifier.KEYBD_MOUSEY; + } } } diff --git a/src/OWML.ModHelper.Menus/NewMenuSystem/OptionsMenuManager.cs b/src/OWML.ModHelper.Menus/NewMenuSystem/OptionsMenuManager.cs index 138777f7..4abe22e0 100644 --- a/src/OWML.ModHelper.Menus/NewMenuSystem/OptionsMenuManager.cs +++ b/src/OWML.ModHelper.Menus/NewMenuSystem/OptionsMenuManager.cs @@ -429,7 +429,7 @@ public GameObject AddSeparator(Menu menu, bool dots) layoutElement.flexibleWidth = 1; layoutElement.preferredHeight = 70; - separatorObj.transform.parent = GetParentForAddedElements(menu); + separatorObj.transform.SetParent(GetParentForAddedElements(menu), false); separatorObj.transform.localScale = Vector3.one; if (!dots) @@ -448,7 +448,7 @@ public GameObject AddSeparator(Menu menu, bool dots) .sprite; var imageObj = new GameObject("dots"); - imageObj.transform.parent = separatorObj.transform; + imageObj.transform.SetParent(separatorObj.transform, false); imageObj.transform.localPosition = Vector3.zero; imageObj.transform.localScale = Vector3.one; @@ -474,7 +474,7 @@ public GameObject AddSeparator(Menu menu, bool dots) public SubmitAction CreateButton(Menu menu, string buttonLabel, string tooltip, MenuSide side) { var rootObj = new GameObject($"UIElement-{buttonLabel}"); - rootObj.transform.parent = GetParentForAddedElements(menu); + rootObj.transform.SetParent(GetParentForAddedElements(menu), false); rootObj.transform.localScale = Vector3.one; rootObj.transform.localRotation = Quaternion.identity; rootObj.transform.localPosition = Vector3.zero; @@ -573,7 +573,7 @@ public SubmitAction CreateButton(Menu menu, string buttonLabel, string tooltip, public SubmitAction CreateButtonWithLabel(Menu menu, string label, string buttonLabel, string tooltip) { var newButtonObj = new GameObject($"UIElement-{label}"); - newButtonObj.transform.parent = GetParentForAddedElements(menu); + newButtonObj.transform.SetParent(GetParentForAddedElements(menu), false); newButtonObj.transform.localScale = Vector3.one; newButtonObj.transform.localRotation = Quaternion.identity; newButtonObj.transform.localPosition = Vector3.zero; @@ -766,12 +766,12 @@ public void CreateLabel(Menu menu, string label, MenuSide side) var textLayoutElement = textObj.AddComponent(); textLayoutElement.minHeight = 70; - textObj.transform.parent = newObj.transform; + textObj.transform.SetParent(newObj.transform, false); textObj.transform.localScale = Vector3.one; textObj.transform.localPosition = Vector3.zero; textObj.transform.localRotation = Quaternion.identity; - newObj.transform.parent = GetParentForAddedElements(menu); + newObj.transform.SetParent(GetParentForAddedElements(menu), false); newObj.transform.localScale = Vector3.one; newObj.transform.localPosition = Vector3.zero; newObj.transform.localRotation = Quaternion.identity; @@ -779,7 +779,7 @@ public void CreateLabel(Menu menu, string label, MenuSide side) public KeyRebindingElement CreateRebinding(Menu menu, string label, string tooltip, RebindableID id) { - _console.WriteLine($"Creating rebinding label:{label} tooltip:{tooltip} id:{id}"); + _console.WriteLine($"Creating rebinding label:{label} tooltip:{tooltip} id:{id}", MessageType.Debug); var existingRebinding = Resources.FindObjectsOfTypeAll() .Single(x => x.name == "InputMenu").transform @@ -787,7 +787,7 @@ public KeyRebindingElement CreateRebinding(Menu menu, string label, string toolt .Find("UIElement-Pause").gameObject; var newRebinding = UnityEngine.Object.Instantiate(existingRebinding); - newRebinding.transform.parent = GetParentForAddedElements(menu); + newRebinding.transform.SetParent(GetParentForAddedElements(menu), false); newRebinding.transform.localScale = Vector3.one; newRebinding.name = $"UIElement-{id}"; diff --git a/src/OWML.ModHelper.Menus/NewMenuSystem/Patches.cs b/src/OWML.ModHelper.Menus/NewMenuSystem/Patches.cs index b7000137..f5b74722 100644 --- a/src/OWML.ModHelper.Menus/NewMenuSystem/Patches.cs +++ b/src/OWML.ModHelper.Menus/NewMenuSystem/Patches.cs @@ -1,14 +1,17 @@ -using System; +using HarmonyLib; +using Newtonsoft.Json.Linq; +using OWML.Common; +using OWML.ModHelper.Input; +using OWML.ModHelper.Menus.CustomInputs; +using OWML.Utils; +using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; -using HarmonyLib; -using OWML.ModHelper.Menus.CustomInputs; +using System.Reflection; using UnityEngine; +using UnityEngine.InputSystem; using UnityEngine.UI; -using OWML.Common; -using OWML.Utils; -using Newtonsoft.Json.Linq; -using OWML.ModHelper.Input; namespace OWML.ModHelper.Menus.NewMenuSystem { @@ -174,7 +177,11 @@ public static void EnableMenu(PopupMenu __instance) [HarmonyReversePatch] [HarmonyPatch(typeof(Menu), nameof(Menu.Activate))] - public static void Menu_Activate_Stub(object instance) { } + public static void Menu_Activate_Stub(Menu __instance) { } + + [HarmonyReversePatch] + [HarmonyPatch(typeof(Menu), nameof(Menu.Deactivate))] + private static void Menu_Deactivate_Stub(Menu __instance, bool remainVisible = false) { } [HarmonyPrefix] [HarmonyPatch(typeof(TabbedMenu), nameof(TabbedMenu.Activate))] @@ -252,5 +259,29 @@ public static bool TabbedMenu_Activate(TabbedMenu __instance) return false; } + + [HarmonyPrefix] + [HarmonyPatch(typeof(TabbedMenu), nameof(TabbedMenu.OnUpdateInputDevice))] + private static bool TabbedMenu_OnUpdateInputDevice(TabbedMenu __instance) + { + if ((object)__instance == null) return false; + if (__instance == null) return false; + if (__instance.gameObject == null) return false; + if (Utils.TypeExtensions.GetValue(__instance, "_tabLeftButtonImg") == null || Utils.TypeExtensions.GetValue(__instance, "_tabRightButtonImg") == null) return false; + return true; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(TabbedMenu), nameof(TabbedMenu.Deactivate))] + private static bool TabbedMenu_Deactivate(TabbedMenu __instance, bool keepPreviousMenuVisible = false) + { + if (Locator.GetEventSystem().currentSelectedGameObject) + Utils.TypeExtensions.SetValue(__instance, "_lastSelectableOnDeactivate", Locator.GetEventSystem().currentSelectedGameObject.GetComponent()); + foreach (var tabSelectablePair in Utils.TypeExtensions.GetValue(__instance, "_tabSelectablePairs")) + tabSelectablePair.tabButton.Enable(false); + Menu_Deactivate_Stub(__instance, keepPreviousMenuVisible); + Locator.GetMenuInputModule().OnInputModuleTab -= __instance.OnInputModuleTabEvent; + return false; + } } } diff --git a/src/OWML.ModHelper.Menus/NewMenuSystem/PopupMenuManager.cs b/src/OWML.ModHelper.Menus/NewMenuSystem/PopupMenuManager.cs index c423a1ff..1a71dfa4 100644 --- a/src/OWML.ModHelper.Menus/NewMenuSystem/PopupMenuManager.cs +++ b/src/OWML.ModHelper.Menus/NewMenuSystem/PopupMenuManager.cs @@ -63,23 +63,28 @@ public void RegisterStartupPopup(string message) Popups.Add(message); } - public PopupMenu CreateTwoChoicePopup(string message, string confirmText, string cancelText) + public void ParentToPopupCanvas(GameObject popup) { - var newPopup = Object.Instantiate(_twoChoicePopupBase); - switch (LoadManager.GetCurrentScene()) { case OWScene.TitleScreen: - newPopup.transform.parent = GameObject.Find("/TitleMenu/PopupCanvas").transform; + popup.transform.SetParent(GameObject.Find("/TitleMenu/PopupCanvas").transform, false); break; case OWScene.SolarSystem: case OWScene.EyeOfTheUniverse: - newPopup.transform.parent = GameObject.Find("/PauseMenu/PopupCanvas").transform; + popup.transform.SetParent(GameObject.Find("/PauseMenu/PopupCanvas").transform, false); break; default: _console.WriteLine($"Cannot create popup in scene {LoadManager.GetCurrentScene()} !", MessageType.Error); break; } + } + + public PopupMenu CreateTwoChoicePopup(string message, string confirmText, string cancelText) + { + var newPopup = Object.Instantiate(_twoChoicePopupBase); + + ParentToPopupCanvas(newPopup); newPopup.transform.localPosition = Vector3.zero; newPopup.transform.localScale = Vector3.one; @@ -102,19 +107,7 @@ public PopupMenu CreateInfoPopup(string message, string continueButtonText) { var newPopup = Object.Instantiate(_twoChoicePopupBase); - switch (LoadManager.GetCurrentScene()) - { - case OWScene.TitleScreen: - newPopup.transform.parent = GameObject.Find("/TitleMenu/PopupCanvas").transform; - break; - case OWScene.SolarSystem: - case OWScene.EyeOfTheUniverse: - newPopup.transform.parent = GameObject.Find("/PauseMenu/PopupCanvas").transform; - break; - default: - _console.WriteLine($"Cannot create popup in scene {LoadManager.GetCurrentScene()} !", MessageType.Error); - break; - } + ParentToPopupCanvas(newPopup); newPopup.transform.localPosition = Vector3.zero; newPopup.transform.localScale = Vector3.one; @@ -137,19 +130,7 @@ public IOWMLThreeChoicePopupMenu CreateThreeChoicePopup(string message, string c { var newPopup = Object.Instantiate(_twoChoicePopupBase); - switch (LoadManager.GetCurrentScene()) - { - case OWScene.TitleScreen: - newPopup.transform.parent = GameObject.Find("/TitleMenu/PopupCanvas").transform; - break; - case OWScene.SolarSystem: - case OWScene.EyeOfTheUniverse: - newPopup.transform.parent = GameObject.Find("/PauseMenu/PopupCanvas").transform; - break; - default: - _console.WriteLine($"Cannot create popup in scene {LoadManager.GetCurrentScene()} !", MessageType.Error); - break; - } + ParentToPopupCanvas(newPopup); newPopup.transform.localPosition = Vector3.zero; newPopup.transform.localScale = Vector3.one; @@ -194,19 +175,7 @@ public IOWMLPopupInputMenu CreateInputFieldPopup(string message, string placehol { var newPopup = Object.Instantiate(_inputPopupBase); - switch (LoadManager.GetCurrentScene()) - { - case OWScene.TitleScreen: - newPopup.transform.parent = GameObject.Find("/TitleMenu/PopupCanvas").transform; - break; - case OWScene.SolarSystem: - case OWScene.EyeOfTheUniverse: - newPopup.transform.parent = GameObject.Find("/PauseMenu/PopupCanvas").transform; - break; - default: - _console.WriteLine($"Cannot create popup in scene {LoadManager.GetCurrentScene()} !", MessageType.Error); - break; - } + ParentToPopupCanvas(newPopup); newPopup.transform.localPosition = Vector3.zero; newPopup.transform.localScale = Vector3.one; @@ -263,19 +232,7 @@ public IOWMLFourChoicePopupMenu CreateFourChoicePopup(string message, string con { var newPopup = Object.Instantiate(_twoChoicePopupBase); - switch (LoadManager.GetCurrentScene()) - { - case OWScene.TitleScreen: - newPopup.transform.parent = GameObject.Find("/TitleMenu/PopupCanvas").transform; - break; - case OWScene.SolarSystem: - case OWScene.EyeOfTheUniverse: - newPopup.transform.parent = GameObject.Find("/PauseMenu/PopupCanvas").transform; - break; - default: - _console.WriteLine($"Cannot create popup in scene {LoadManager.GetCurrentScene()} !", MessageType.Error); - break; - } + ParentToPopupCanvas(newPopup); newPopup.transform.localPosition = Vector3.zero; newPopup.transform.localScale = Vector3.one; diff --git a/src/OWML.ModLoader/Owo.cs b/src/OWML.ModLoader/Owo.cs index d799075a..78370147 100644 --- a/src/OWML.ModLoader/Owo.cs +++ b/src/OWML.ModLoader/Owo.cs @@ -282,33 +282,5 @@ private IModBehaviour InitializeMod(Type modType, IModHelper helper) return null; } } - - [HarmonyPrefix] - [HarmonyPatch(typeof(TabbedMenu), nameof(TabbedMenu.OnUpdateInputDevice))] - private static bool TabbedMenu_OnUpdateInputDevice(TabbedMenu __instance) - { - if ((object)__instance == null) return false; - if (__instance == null) return false; - if (__instance.gameObject == null) return false; - if (Utils.TypeExtensions.GetValue(__instance, "_tabLeftButtonImg") == null || Utils.TypeExtensions.GetValue(__instance, "_tabRightButtonImg") == null) return false; - return true; - } - - [HarmonyReversePatch] - [HarmonyPatch(typeof(Menu), nameof(Menu.Deactivate))] - private static void Menu_Deactivate(Menu __instance, bool remainVisible = false) { } - - [HarmonyPrefix] - [HarmonyPatch(typeof(TabbedMenu), nameof(TabbedMenu.Deactivate))] - private static bool TabbedMenu_Deactivate(TabbedMenu __instance, bool keepPreviousMenuVisible = false) - { - if (Locator.GetEventSystem().currentSelectedGameObject) - Utils.TypeExtensions.SetValue(__instance, "_lastSelectableOnDeactivate", Locator.GetEventSystem().currentSelectedGameObject.GetComponent()); - foreach (var tabSelectablePair in Utils.TypeExtensions.GetValue(__instance, "_tabSelectablePairs")) - tabSelectablePair.tabButton.Enable(false); - Menu_Deactivate(__instance, keepPreviousMenuVisible); - Locator.GetMenuInputModule().OnInputModuleTab -= __instance.OnInputModuleTabEvent; - return false; - } } } diff --git a/src/OWML.ModLoader/Patches.cs b/src/OWML.ModLoader/Patches.cs new file mode 100644 index 00000000..6d425a3d --- /dev/null +++ b/src/OWML.ModLoader/Patches.cs @@ -0,0 +1,307 @@ +using HarmonyLib; +using Newtonsoft.Json.Linq; +using OWML.Common; +using OWML.ModHelper.Menus.NewMenuSystem; +using OWML.Utils; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; +using UnityEngine.InputSystem; +using static UnityEngine.InputSystem.InputBinding; + +namespace OWML.ModLoader +{ + [HarmonyPatch] + public static class Patches + { + /// + /// Modified TryGetAxisIdentifier that removes the device prefix of more than just gamepad. + /// + /// + /// Fixes devices other than gamepad not being able to see button prompt for positive/negative axis inputs + /// + [HarmonyPatch] + public static class InputTransitionUtilTryGetAxisIdentifierTwoPathsPatch + { + public static MethodBase TargetMethod() => AccessTools.Method( + typeof(InputTransitionUtil), + nameof(InputTransitionUtil.TryGetAxisIdentifier), + new[] + { + typeof(string), + typeof(string), + typeof(AxisIdentifier).MakeByRefType() + } + ); + + [HarmonyPrefix] + public static bool Prefix( + string firstControlPath, + string secondControlPath, + ref AxisIdentifier axisID, + ref bool __result) + { + axisID = AxisIdentifier.NONE; + + if (!TryGetAxisKey(firstControlPath, secondControlPath, out var key)) + { + __result = false; + return false; + } + + __result = TryGetAxisID(key, out axisID); + return false; + } + + private static bool TryGetAxisID(string key, out AxisIdentifier axisID) + { + return InputTransitionUtil.AxisIDCache.TryGetValue(key, out axisID); + } + + private static bool TryGetAxisKey(string firstPath, string secondPath, out string key) + { + key = null; + + if (string.IsNullOrEmpty(firstPath) || string.IsNullOrEmpty(secondPath)) + return false; + + var first = firstPath.Split(new[] { ControlPathConstants.PATH_SEPARATOR_CHAR }, StringSplitOptions.RemoveEmptyEntries); + var second = secondPath.Split(new[] { ControlPathConstants.PATH_SEPARATOR_CHAR }, StringSplitOptions.RemoveEmptyEntries); + + if (first.Length == 0 || second.Length == 0) + return false; + + var axis = GetAxis(first[first.Length - 1], second[second.Length - 1]); + if (string.IsNullOrEmpty(axis)) + return false; + + var start = IsDevicePrefix(first[0]) ? 1 : 0; + first[first.Length - 1] = axis; + + key = string.Join(ControlPathConstants.PATH_SEPARATOR, first, start, first.Length - start); + return true; + } + + private static string GetAxis(string first, string second) + { + if ((first == ControlPathConstants.UP && second == ControlPathConstants.DOWN) || (first == ControlPathConstants.DOWN && second == ControlPathConstants.UP)) + return ControlPathConstants.Y; + + if ((first == ControlPathConstants.LEFT && second == ControlPathConstants.RIGHT) || (first == ControlPathConstants.RIGHT && second == ControlPathConstants.LEFT)) + return ControlPathConstants.X; + + return null; + } + + private static bool IsDevicePrefix(string part) + { + return part == ControlPathConstants.Gamepad.DEVICE || part == ControlPathConstants.Mouse.DEVICE || part == ControlPathConstants.Keyboard.DEVICE; + } + } + + [HarmonyPatch] + public static class InputActionUtilPopulateUITextureListPatch + { + public static MethodBase TargetMethod() => AccessTools.Method( + typeof(InputActionUtil), + nameof(InputActionUtil.PopulateUITextureList), + new[] + { + typeof(InputAction), + typeof(List).MakeByRefType(), + typeof(bool) + } + ); + + [HarmonyPrefix] + public static bool Prefix( + InputAction action, + List textureList, + bool gamepad, + ref bool __result) + { + if (action == null) + return true; + + var mask = gamepad + ? InputActionUtil.GamepadBindingMask + : InputActionUtil.DesktopBindingMask; + + int index = action.GetBindingIndex(mask); + if (index == -1) + return true; + + action.GetBindingDisplayString( + index, + out var deviceLayoutName, + out var controlPath, + DisplayStringOptions.DontUseShortDisplayNames + ); + + // Control paths that aren't attached to an actual control will be empty and return no images, but we don't want that. + if (!string.IsNullOrEmpty(controlPath)) + return true; + + textureList.Add(ButtonPromptLibrary.s_testButton); + + __result = true; + return false; + } + } + + + /// + /// Fixes mouse delta and scroll input handling in BasicInputAction, AxisInputAction, and InputActionPair + /// + /// + /// The original code only supports mouse delta input for the "x" and "y" (not "up", "down", "left", "right"), and didn't support scroll input at all. + /// + [HarmonyPatch] + public static class MouseAxisControlPatch + { + private static readonly FieldInfo MouseDeltaField = + AccessTools.Field(typeof(BaseInputManager), nameof(BaseInputManager.MouseDelta)); + + private static readonly FieldInfo Vector2XField = + AccessTools.Field(typeof(Vector2), nameof(Vector2.x)); + + private static readonly FieldInfo Vector2YField = + AccessTools.Field(typeof(Vector2), nameof(Vector2.y)); + + private static readonly MethodInfo ResolveMethod = + AccessTools.Method(typeof(MouseAxisControlPatch), nameof(ResolveMouseAxis)); + + [HarmonyTranspiler] + [HarmonyPatch(typeof(BasicInputAction), nameof(BasicInputAction.DoUpdate))] + [HarmonyPatch(typeof(AxisInputAction), nameof(AxisInputAction.DoUpdate))] + [HarmonyPatch(typeof(InputActionPair), nameof(InputActionPair.DoUpdate))] + private static IEnumerable PatchWithMatcher( + IEnumerable instructions) + { + var matcher = new CodeMatcher(instructions); + + while (true) + { + // if (this._mouseAxisControl.name == "x") + // { + // value = BaseInputManager.MouseDelta.x; + // } + // else + // { + // value = BaseInputManager.MouseDelta.y; + // } + matcher.MatchForward(false, + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Ldfld), + new CodeMatch(IsNameGetter), + new CodeMatch(OpCodes.Ldstr, "x"), + new CodeMatch(IsStringEquals), + new CodeMatch(IsBranchFalse), + new CodeMatch(IsMouseDeltaLoad), + new CodeMatch(IsVector2AxisField), + new CodeMatch(IsStoreLocal), + new CodeMatch(IsBranch), + new CodeMatch(IsMouseDeltaLoad), + new CodeMatch(IsVector2AxisField), + new CodeMatch(IsStoreLocal) + ); + + if (!matcher.IsValid) + break; + + var labels = matcher.Instruction.labels; + var field = matcher.InstructionAt(1).operand as FieldInfo; + var store = matcher.InstructionAt(8).Clone(); + + // value = MouseAxisControlPatch.ResolveMouseAxis(this._mouseAxisControl); + matcher + .RemoveInstructions(13) + .Insert( + new CodeInstruction(OpCodes.Ldarg_0).WithLabels(labels), + new CodeInstruction(OpCodes.Ldfld, field), + new CodeInstruction(OpCodes.Call, ResolveMethod), + store + ); + } + + return matcher.InstructionEnumeration(); + } + + private static float ResolveMouseAxis(InputControl control) + { + if (control == null) + return 0f; + + var mouse = Mouse.current; + if (mouse == null) + return 0f; + + string path = control.path; // e.g. "/Mouse/delta/x" + + Vector2 vector = path.Contains("/scroll") + ? Mouse.current.scroll.ReadValue() + : BaseInputManager.MouseDelta; + + return path switch + { + var p when p.EndsWith("/x") => vector.x, + var p when p.EndsWith("/y") => vector.y, + + var p when p.EndsWith("/right") => Mathf.Max(vector.x, 0f), + var p when p.EndsWith("/left") => Mathf.Max(-vector.x, 0f), + + var p when p.EndsWith("/up") => Mathf.Max(vector.y, 0f), + var p when p.EndsWith("/down") => Mathf.Max(-vector.y, 0f), + + _ => control.ReadValueAsObject() is float value ? value : 0f + }; + } + + + private static bool IsMouseDeltaLoad(CodeInstruction i) + { + return (i.opcode == OpCodes.Ldsfld || i.opcode == OpCodes.Ldsflda) + && Equals(i.operand, MouseDeltaField); + } + + private static bool IsVector2AxisField(CodeInstruction i) + { + return i.opcode == OpCodes.Ldfld + && (Equals(i.operand, Vector2XField) || Equals(i.operand, Vector2YField)); + } + + private static bool IsNameGetter(CodeInstruction i) + { + return i.opcode == OpCodes.Callvirt + && Equals(i.operand, AccessTools.PropertyGetter(typeof(InputControl), nameof(InputControl.name))); + } + + private static bool IsStringEquals(CodeInstruction i) + { + return i.opcode == OpCodes.Call + && Equals(i.operand, AccessTools.Method(typeof(string), "op_Equality")); + } + + private static bool IsStoreLocal(CodeInstruction i) + { + return i.opcode == OpCodes.Stloc_0 + || i.opcode == OpCodes.Stloc_1 + || i.opcode == OpCodes.Stloc_2 + || i.opcode == OpCodes.Stloc_3 + || i.opcode == OpCodes.Stloc_S; + } + + private static bool IsBranch(CodeInstruction i) + { + return i.opcode == OpCodes.Br || i.opcode == OpCodes.Br_S; + } + + private static bool IsBranchFalse(CodeInstruction i) + { + return i.opcode == OpCodes.Brfalse || i.opcode == OpCodes.Brfalse_S; + } + } + } +} diff --git a/src/OWML.Utils/InputCommandExtensions.cs b/src/OWML.Utils/InputCommandExtensions.cs new file mode 100644 index 00000000..3ebff5c5 --- /dev/null +++ b/src/OWML.Utils/InputCommandExtensions.cs @@ -0,0 +1,176 @@ +using OWML.Common; +using OWML.Common.Enums; +using System; +using UnityEngine.InputSystem; +using static InputConsts; + +namespace OWML.Utils +{ + public static class InputCommandExtensions + { + public static IInputCommands GetInputCommand(this InputCommandType type) + => InputLibrary.GetInputCommand(type); + + public static string GetInputCommandPath(this Key key) + { + return ControlPathConstants.Keyboard.DEVICE + ControlPathConstants.PATH_SEPARATOR + (key switch + { + Key.None => ControlPathConstants.NONE, + + Key.Space => ControlPathConstants.Keyboard.SPACE, + Key.Enter => ControlPathConstants.Keyboard.ENTER, + Key.Tab => ControlPathConstants.Keyboard.TAB, + Key.Backquote => ControlPathConstants.Keyboard.BACKQUOTE, + Key.Quote => ControlPathConstants.Keyboard.QUOTE, + Key.Semicolon => ControlPathConstants.Keyboard.SEMICOLON, + Key.Comma => ControlPathConstants.Keyboard.COMMA, + Key.Period => ControlPathConstants.Keyboard.PERIOD, + Key.Slash => ControlPathConstants.Keyboard.SLASH, + Key.Backslash => ControlPathConstants.Keyboard.BACKSLASH, + Key.LeftBracket => ControlPathConstants.Keyboard.LEFT_BRACKET, + Key.RightBracket => ControlPathConstants.Keyboard.RIGHT_BRACKET, + Key.Minus => ControlPathConstants.Keyboard.MINUS, + Key.Equals => ControlPathConstants.Keyboard.EQUALS, + + >= Key.A and <= Key.Z => key.ToString().ToLowerInvariant(), + + >= Key.Digit1 and <= Key.Digit0 => ((int)key - (int)Key.Digit1 + 1) % 10, + + Key.LeftShift => ControlPathConstants.Keyboard.LEFT_SHIFT, + Key.RightShift => ControlPathConstants.Keyboard.RIGHT_SHIFT, + Key.LeftAlt => ControlPathConstants.Keyboard.LEFT_ALT, + Key.RightAlt => ControlPathConstants.Keyboard.RIGHT_ALT, + Key.LeftCtrl => ControlPathConstants.Keyboard.LEFT_CTRL, + Key.RightCtrl => ControlPathConstants.Keyboard.RIGHT_CTRL, + Key.LeftMeta => ControlPathConstants.Keyboard.LEFT_META, + Key.RightMeta => ControlPathConstants.Keyboard.RIGHT_META, + Key.ContextMenu => ControlPathConstants.Keyboard.CONTEXT_MENU, + + Key.Escape => ControlPathConstants.Keyboard.ESCAPE, + Key.LeftArrow => ControlPathConstants.Keyboard.LEFT_ARROW, + Key.RightArrow => ControlPathConstants.Keyboard.RIGHT_ARROW, + Key.UpArrow => ControlPathConstants.Keyboard.UP_ARROW, + Key.DownArrow => ControlPathConstants.Keyboard.DOWN_ARROW, + Key.Backspace => ControlPathConstants.Keyboard.BACKSPACE, + Key.PageDown => ControlPathConstants.Keyboard.PAGE_DOWN, + Key.PageUp => ControlPathConstants.Keyboard.PAGE_UP, + Key.Home => ControlPathConstants.Keyboard.HOME, + Key.End => ControlPathConstants.Keyboard.END, + Key.Insert => ControlPathConstants.Keyboard.INSERT, + Key.Delete => ControlPathConstants.Keyboard.DELETE, + + Key.CapsLock => ControlPathConstants.Keyboard.CAPS_LOCK, + Key.NumLock => ControlPathConstants.Keyboard.NUM_LOCK, + Key.PrintScreen => ControlPathConstants.Keyboard.PRINT_SCREEN, + Key.ScrollLock => ControlPathConstants.Keyboard.SCROLL_LOCK, + Key.Pause => ControlPathConstants.Keyboard.PAUSE, + + Key.NumpadEnter => ControlPathConstants.Keyboard.NUMPAD_ENTER, + Key.NumpadDivide => ControlPathConstants.Keyboard.NUMPAD_DIVIDE, + Key.NumpadMultiply => ControlPathConstants.Keyboard.NUMPAD_MULTIPLY, + Key.NumpadPlus => ControlPathConstants.Keyboard.NUMPAD_PLUS, + Key.NumpadMinus => ControlPathConstants.Keyboard.NUMPAD_MINUS, + Key.NumpadPeriod => ControlPathConstants.Keyboard.NUMPAD_PERIOD, + Key.NumpadEquals => ControlPathConstants.Keyboard.NUMPAD_EQUALS, + + >= Key.Numpad0 and <= Key.Numpad9 => ControlPathConstants.Keyboard.NUMPAD + ((int)key - (int)Key.Numpad0), + + >= Key.F1 and <= Key.F12 => ControlPathConstants.Keyboard.F + ((int)key - (int)Key.F1 + 1), + + >= Key.OEM1 and <= Key.OEM5 => ControlPathConstants.Keyboard.OEM + ((int)key - (int)Key.OEM1 + 1), + + _ => throw new NotImplementedException($"Key {key} is not implemented in GetInputCommandPath."), + }); + } + + public static string GetInputCommandPath(this MouseBinding binding) + { + return ControlPathConstants.Mouse.DEVICE + ControlPathConstants.PATH_SEPARATOR + (binding switch + { + MouseBinding.None => ControlPathConstants.NONE, + + MouseBinding.Left => ControlPathConstants.Mouse.LEFT_BUTTON, + MouseBinding.Right => ControlPathConstants.Mouse.RIGHT_BUTTON, + MouseBinding.Middle => ControlPathConstants.Mouse.MIDDLE_BUTTON, + + MouseBinding.Back => ControlPathConstants.Mouse.BACK_BUTTON, + MouseBinding.Forward => ControlPathConstants.Mouse.FORWARD_BUTTON, + + MouseBinding.DeltaUp => ControlPathConstants.Mouse.DELTA_UP, + MouseBinding.DeltaDown => ControlPathConstants.Mouse.DELTA_DOWN, + MouseBinding.DeltaLeft => ControlPathConstants.Mouse.DELTA_LEFT, + MouseBinding.DeltaRight => ControlPathConstants.Mouse.DELTA_RIGHT, + + MouseBinding.ScrollUp => ControlPathConstants.Mouse.SCROLL_UP, + MouseBinding.ScrollDown => ControlPathConstants.Mouse.SCROLL_DOWN, + MouseBinding.ScrollLeft => ControlPathConstants.Mouse.SCROLL_LEFT, + MouseBinding.ScrollRight => ControlPathConstants.Mouse.SCROLL_RIGHT, + + _ => throw new NotImplementedException($"MouseBinding {binding} is not implemented in GetInputCommandPath."), + }); + } + + public static string GetInputCommandPath(this GamepadBinding binding) + { + return ControlPathConstants.Gamepad.DEVICE + ControlPathConstants.PATH_SEPARATOR + (binding switch + { + GamepadBinding.None => ControlPathConstants.NONE, + + GamepadBinding.DPadUp => ControlPathConstants.Gamepad.DPAD_UP, + GamepadBinding.DPadDown => ControlPathConstants.Gamepad.DPAD_DOWN, + GamepadBinding.DPadLeft => ControlPathConstants.Gamepad.DPAD_LEFT, + GamepadBinding.DPadRight => ControlPathConstants.Gamepad.DPAD_RIGHT, + + GamepadBinding.UpButton => ControlPathConstants.Gamepad.BUTTON_NORTH, + GamepadBinding.DownButton => ControlPathConstants.Gamepad.BUTTON_SOUTH, + GamepadBinding.LeftButton => ControlPathConstants.Gamepad.BUTTON_EAST, + GamepadBinding.RightButton => ControlPathConstants.Gamepad.BUTTON_WEST, + + GamepadBinding.LeftStickClick => ControlPathConstants.Gamepad.LEFT_STICK_PRESS, + GamepadBinding.RightStickClick => ControlPathConstants.Gamepad.RIGHT_STICK_PRESS, + + GamepadBinding.LeftShoulder => ControlPathConstants.Gamepad.LEFT_SHOULDER, + GamepadBinding.RightShoulder => ControlPathConstants.Gamepad.RIGHT_SHOULDER, + + GamepadBinding.Start => ControlPathConstants.Gamepad.START, + GamepadBinding.Select => ControlPathConstants.Gamepad.SELECT, + + GamepadBinding.LeftTrigger => ControlPathConstants.Gamepad.LEFT_TRIGGER, + GamepadBinding.RightTrigger => ControlPathConstants.Gamepad.RIGHT_TRIGGER, + + GamepadBinding.Share => ControlPathConstants.Gamepad.SHARE, + GamepadBinding.SystemButton => ControlPathConstants.Gamepad.SYSTEM_BUTTON, + + GamepadBinding.LeftStickUp => ControlPathConstants.Gamepad.LEFT_STICK_UP, + GamepadBinding.LeftStickDown => ControlPathConstants.Gamepad.LEFT_STICK_DOWN, + GamepadBinding.LeftStickLeft => ControlPathConstants.Gamepad.LEFT_STICK_LEFT, + GamepadBinding.LeftStickRight => ControlPathConstants.Gamepad.LEFT_STICK_RIGHT, + + GamepadBinding.RightStickUp => ControlPathConstants.Gamepad.RIGHT_STICK_UP, + GamepadBinding.RightStickDown => ControlPathConstants.Gamepad.RIGHT_STICK_DOWN, + GamepadBinding.RightStickLeft => ControlPathConstants.Gamepad.RIGHT_STICK_LEFT, + GamepadBinding.RightStickRight => ControlPathConstants.Gamepad.RIGHT_STICK_RIGHT, + + _ => throw new NotImplementedException($"GamepadBinding {binding} is not implemented in GetInputCommandPath."), + }); + } + + public static string GetInputCommandPath(this InputDevice device) + => device switch + { + Gamepad => ControlPathConstants.Gamepad.DEVICE, + Mouse => ControlPathConstants.Mouse.DEVICE, + Keyboard => ControlPathConstants.Keyboard.DEVICE, + _ => $"<{device.path.TrimStart(ControlPathConstants.PATH_SEPARATOR_CHAR)}>", + }; + + public static string GetInputCommandPath(this InputControl control) + { + string devicePath = control.device.GetInputCommandPath(); + + int startIndex = control.path.IndexOf(ControlPathConstants.PATH_SEPARATOR_CHAR, 1); + + return devicePath + control.path.Substring(startIndex); + } + } +} \ No newline at end of file diff --git a/src/SampleMods/OWML.MenuExample/DebugGUI.cs b/src/SampleMods/OWML.MenuExample/DebugGUI.cs index af08861f..119e2336 100644 --- a/src/SampleMods/OWML.MenuExample/DebugGUI.cs +++ b/src/SampleMods/OWML.MenuExample/DebugGUI.cs @@ -116,6 +116,24 @@ private void DrawGui() DrawDualBar(GetComponent().rebindDualAxis07Threshold); DrawDualBar(InputLibrary.toolOptionX.CommandType); + + // Mouse inputs: buttons, delta (movement), and scroll + DrawBar(GetComponent().rebindMouseLeft); + DrawBar(GetComponent().rebindMouseRight); + DrawBar(GetComponent().rebindMouseMiddle); + DrawDualBar(GetComponent().rebindMouseExtra); + + var mx = InputLibrary.GetInputCommand(GetComponent().rebindDeltaX); + var mxVal = OWInput.GetValue(mx); + var my = InputLibrary.GetInputCommand(GetComponent().rebindDeltaY); + var myVal = OWInput.GetValue(my); + DrawCircle(new Vector2(200 + mxVal * 200, 200 - myVal * 200), 5, Color.magenta, 1); + + var sx = InputLibrary.GetInputCommand(GetComponent().rebindScrollX); + var sxVal = OWInput.GetValue(sx); + var sy = InputLibrary.GetInputCommand(GetComponent().rebindScrollY); + var syVal = OWInput.GetValue(sy); + DrawCircle(new Vector2(200 + sxVal * 200, 200 - syVal * 200), 5, Color.cyan, 1); } private Texture2D _tex; diff --git a/src/SampleMods/OWML.MenuExample/MenuExample.cs b/src/SampleMods/OWML.MenuExample/MenuExample.cs index 890eb13a..7a127ef6 100644 --- a/src/SampleMods/OWML.MenuExample/MenuExample.cs +++ b/src/SampleMods/OWML.MenuExample/MenuExample.cs @@ -25,11 +25,13 @@ public void Start() rebindY = ModHelper.RebindingHelper.RegisterRebindable("Test Y (Dual)", "Test Tooltip Y", "/w", "/leftStick/up", "/s", "/leftStick/down"); rebindComp = ModHelper.RebindingHelper.RegisterComposite("Test Composite", rebindY, rebindX);*/ - rebindXButton = ModHelper.RebindingHelper.RegisterRebindable("Test X (Button)", "", Key.A, GamepadBinding.LeftStickRight, Key.D, GamepadBinding.LeftStickLeft, false); + rebindXButton = ModHelper.RebindingHelper.RegisterRebindable("Test X (Button)", "", Key.D, GamepadBinding.LeftStickRight, Key.A, GamepadBinding.LeftStickLeft, false); + ModHelper.RebindingHelper.FlipImages(rebindXButton); rebindYButton = ModHelper.RebindingHelper.RegisterRebindable("Test Y (Button)", "", Key.W, GamepadBinding.LeftStickUp, Key.S, GamepadBinding.LeftStickDown, false); rebindCompButton = ModHelper.RebindingHelper.RegisterComposite("Test Composite Button", rebindXButton, rebindYButton); - rebindXAxis = ModHelper.RebindingHelper.RegisterRebindable("Test X (Axis)", "", Key.A, GamepadBinding.LeftStickRight, Key.D, GamepadBinding.LeftStickLeft, true); + rebindXAxis = ModHelper.RebindingHelper.RegisterRebindable("Test X (Axis)", "", Key.D, GamepadBinding.LeftStickRight, Key.A, GamepadBinding.LeftStickLeft, true); + ModHelper.RebindingHelper.FlipImages(rebindXAxis); rebindYAxis = ModHelper.RebindingHelper.RegisterRebindable("Test Y (Axis)", "", Key.W, GamepadBinding.LeftStickUp, Key.S, GamepadBinding.LeftStickDown, true); rebindCompAxis = ModHelper.RebindingHelper.RegisterComposite("Test Composite Axis", rebindXAxis, rebindYAxis); @@ -42,6 +44,19 @@ public void Start() rebindDualButton07Threshold = ModHelper.RebindingHelper.RegisterRebindable("Test (Dual Button) 0.7", "", Key.Z, GamepadBinding.RightTrigger, Key.X, GamepadBinding.LeftTrigger, false, 0.7f); rebindDualAxis = ModHelper.RebindingHelper.RegisterRebindable("Test (Dual Axis)", "", Key.Z, GamepadBinding.RightTrigger, Key.X, GamepadBinding.LeftTrigger, true); rebindDualAxis07Threshold = ModHelper.RebindingHelper.RegisterRebindable("Test (Dual Axis) 0.7", "", Key.Z, GamepadBinding.RightTrigger, Key.X, GamepadBinding.LeftTrigger, true, 0.7f); + + rebindMouseLeft = ModHelper.RebindingHelper.RegisterRebindable("Test Mouse Left (Button)", "", MouseBinding.Left, GamepadBinding.LeftShoulder, false); + rebindMouseRight = ModHelper.RebindingHelper.RegisterRebindable("Test Mouse Right (Button)", "", MouseBinding.Right, GamepadBinding.RightShoulder, false); + rebindMouseMiddle = ModHelper.RebindingHelper.RegisterRebindable("Test Mouse Middle (Button)", "", MouseBinding.Middle, GamepadBinding.None, false); + rebindMouseExtra = ModHelper.RebindingHelper.RegisterRebindable("Test Mouse Extra (Axis)", "", MouseBinding.Forward, GamepadBinding.None, MouseBinding.Back, GamepadBinding.None, true); + + rebindDeltaX = ModHelper.RebindingHelper.RegisterRebindable("Test Delta X (Axis)", "", MouseBinding.DeltaRight, GamepadBinding.RightStickRight, MouseBinding.DeltaLeft, GamepadBinding.RightStickLeft, true); + ModHelper.RebindingHelper.FlipImages(rebindDeltaX); + rebindDeltaY = ModHelper.RebindingHelper.RegisterRebindable("Test Delta Y (Axis)", "", MouseBinding.DeltaUp, GamepadBinding.RightStickUp, MouseBinding.DeltaDown, GamepadBinding.RightStickDown, true); + + rebindScrollY = ModHelper.RebindingHelper.RegisterRebindable("Test Scroll Y (Axis)", "", MouseBinding.ScrollUp, GamepadBinding.DPadUp, MouseBinding.ScrollDown, GamepadBinding.DPadDown, true); + rebindScrollX = ModHelper.RebindingHelper.RegisterRebindable("Test Scroll X (Axis)", "", MouseBinding.ScrollRight, GamepadBinding.DPadRight, MouseBinding.ScrollLeft, GamepadBinding.DPadLeft, true); + ModHelper.RebindingHelper.FlipImages(rebindScrollX); } public IOWMLFourChoicePopupMenu FourChoicePopupMenu; @@ -71,6 +86,17 @@ public void Start() public InputConsts.InputCommandType rebindDualAxis; public InputConsts.InputCommandType rebindDualAxis07Threshold; + // Mouse rebinds + public InputConsts.InputCommandType rebindMouseLeft; + public InputConsts.InputCommandType rebindMouseRight; + public InputConsts.InputCommandType rebindMouseMiddle; + public InputConsts.InputCommandType rebindMouseExtra; + + public InputConsts.InputCommandType rebindDeltaX; + public InputConsts.InputCommandType rebindDeltaY; + public InputConsts.InputCommandType rebindScrollY; + public InputConsts.InputCommandType rebindScrollX; + public override void SetupTitleMenu(ITitleMenuManager titleManager) { var infoButton = titleManager.CreateTitleButton("INFO POPUP");