From 041c39d7ff81ba306807eb95d6a17197cf2a03a4 Mon Sep 17 00:00:00 2001 From: CallOfCreator <177672587+CallOfCreator@users.noreply.github.com> Date: Sun, 27 Sep 2026 16:14:56 +0000 Subject: [PATCH] Controller Support --- MiraAPI.Example/Buttons/ThinkButton.cs | 5 +- MiraAPI.Example/ExampleKeybinds.cs | 6 +- MiraAPI/Hud/CustomActionButton.cs | 172 +++++------------- MiraAPI/Keybinds/BaseKeybind.cs | 9 +- MiraAPI/Keybinds/ControllerButton.cs | 87 +++++++++ MiraAPI/Keybinds/KeybindManager.cs | 107 ++++++++--- MiraAPI/Keybinds/KeybindUtils.cs | 49 ++++- MiraAPI/Keybinds/MiraGlobalKeybinds.cs | 10 +- MiraAPI/Keybinds/MiraKeybind.cs | 29 ++- .../RegisterCustomKeybindsAttribute.cs | 4 +- MiraAPI/Keybinds/VanillaKeybind.cs | 4 +- MiraAPI/Keybinds/VanillaKeybindSingleton.cs | 7 +- MiraAPI/Patches/HudManagerPatches.cs | 155 ++++------------ .../Patches/Keybinds/ControlMapperPatches.cs | 91 +++++---- MiraAPI/Patches/Keybinds/KeybindsPatch.cs | 15 +- .../Keybinds/OptionsMenuBehaviourPatch.cs | 24 +-- .../Patches/Keybinds/PlayerKeybindPatches.cs | 64 +++++++ MiraAPI/Utilities/Extensions.cs | 154 ++++------------ 18 files changed, 516 insertions(+), 476 deletions(-) create mode 100644 MiraAPI/Keybinds/ControllerButton.cs create mode 100644 MiraAPI/Patches/Keybinds/PlayerKeybindPatches.cs diff --git a/MiraAPI.Example/Buttons/ThinkButton.cs b/MiraAPI.Example/Buttons/ThinkButton.cs index 71013e9..1141a9a 100644 --- a/MiraAPI.Example/Buttons/ThinkButton.cs +++ b/MiraAPI.Example/Buttons/ThinkButton.cs @@ -1,4 +1,5 @@ using MiraAPI.Hud; +using MiraAPI.Keybinds; using MiraAPI.Translation; using MiraAPI.Utilities.Assets; using UnityEngine; @@ -13,6 +14,8 @@ public class ThinkButton : CustomActionButton public override int MaxUses => 1; public override ButtonUsesMode UsesMode => ButtonUsesMode.PerRound; public override LoadableAsset Sprite => ExampleAssets.ExampleButton; + public override MiraKeybind Keybind => MiraGlobalKeybinds.SecondaryAbility; + protected override void OnClick() { Button!.OverrideText("button.think.thinking".Translate()); @@ -32,4 +35,4 @@ public class ThinkButton : CustomActionButton { return role != null && role.IsImpostor; } -} +} \ No newline at end of file diff --git a/MiraAPI.Example/ExampleKeybinds.cs b/MiraAPI.Example/ExampleKeybinds.cs index dba6b67..601fc28 100644 --- a/MiraAPI.Example/ExampleKeybinds.cs +++ b/MiraAPI.Example/ExampleKeybinds.cs @@ -1,4 +1,4 @@ -using MiraAPI.Keybinds; +using MiraAPI.Keybinds; using Rewired; namespace MiraAPI.Example; @@ -6,5 +6,5 @@ namespace MiraAPI.Example; [RegisterCustomKeybinds] public static class ExampleKeybinds { - public static MiraKeybind NeutralWinKeybind { get; } = new("Neutral Win", KeyboardKeyCode.C, [ModifierKey.Shift]); -} + public static MiraKeybind NeutralWinKeybind { get; } = new("Neutral Win", KeyboardKeyCode.C, ControllerButton.RightStick, [ModifierKey.Shift]); +} \ No newline at end of file diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index abc762e..bd56260 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -139,10 +139,7 @@ public abstract class CustomActionButton /// The parent of the button. public virtual void CreateButton(Transform parent) { - if (Button) - { - return; - } + if (Button) return; UsesLeft = MaxUses; Timer = AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay ? 0 : InitialCooldown; @@ -152,19 +149,15 @@ public abstract class CustomActionButton Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent); Button.name = Name + "Button"; Button.OverrideText(Name.Translate().ToUpperInvariant()); + Object.Destroy(Button.glyph.GetComponent()); + Object.Destroy(Button.glyph.GetComponent()); Button.graphic.sprite = Sprite.LoadAsset(); Button.SetUsesRemaining(MaxUses); - if (MaxUses <= 0) - { - Button.SetInfiniteUses(); - } + if (MaxUses <= 0) Button.SetInfiniteUses(); - if (TextOutlineColor != Color.clear) - { - SetTextOutline(TextOutlineColor); - } + if (TextOutlineColor != Color.clear) SetTextOutline(TextOutlineColor); var pb = Button.GetComponent(); pb.OnClick = new Button.ButtonClickedEvent(); @@ -173,10 +166,7 @@ public abstract class CustomActionButton // Invoke the generic button click event. var genericEvent = new MiraButtonClickEvent(this); MiraEventManager.InvokeEvent(genericEvent); - if (genericEvent.IsCancelled) - { - MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this)); - } + if (genericEvent.IsCancelled) MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this)); // Invoke the button click event for specific button. var eventType = CustomButtonManager.ButtonEventTypes[GetType()]; @@ -191,68 +181,47 @@ public abstract class CustomActionButton if (specificInvoked) { - if (!@event.IsCancelled) - { - ClickHandler(); - } + if (!@event.IsCancelled) ClickHandler(); } else { - if (!genericEvent.IsCancelled) - { - ClickHandler(); - } + if (!genericEvent.IsCancelled) ClickHandler(); } })); if (Keybind == null) return; - Keybind.OnActivate(() => - { - if (!Enabled(PlayerControl.LocalPlayer.Data.Role)) return; + Keybind.RemoveOnActivate(OnKeybind); + Keybind.OnActivate(OnKeybind); - // Invoke the generic button click event. - var genericEvent = new MiraButtonClickEvent(this); - MiraEventManager.InvokeEvent(genericEvent); - if (genericEvent.IsCancelled) - { - MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this)); - } - - // Invoke the button click event for specific button. - var eventType = CustomButtonManager.ButtonEventTypes[GetType()]; - var @event = (MiraCancelableEvent)Activator.CreateInstance(eventType, this, genericEvent)!; - var specificInvoked = MiraEventManager.InvokeEvent(@event, eventType); - if (@event.IsCancelled) - { - var cancelEventType = CustomButtonManager.ButtonCancelledEventTypes[GetType()]; - var cancelEvent = (MiraEvent)Activator.CreateInstance(cancelEventType, this)!; - MiraEventManager.InvokeEvent(cancelEvent, cancelEventType); - } - - if (specificInvoked) - { - if (!@event.IsCancelled) - { - ClickHandler(); - } - } - else - { - if (!genericEvent.IsCancelled) - { - ClickHandler(); - } - } - }); - - KeybindIcon = - Helpers.CreateKeybindIcon( - Button.gameObject, - Keybind.CurrentKey, - new Vector3(0.4f, 0.45f, -9f)); + KeybindIcon = Helpers.CreateKeybindIcon(Button.gameObject, Keybind.CurrentKey, new Vector3(0.4f, 0.45f, -9f)); KeybindText = KeybindIcon.transform.GetChild(0).GetComponent(); - HudManagerPatches.ModdedKeybindIcons.Add(KeybindText); - Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f); + KeybindText.fontSizeMax = KeybindText.fontSize; + KeybindText.fontSizeMin = KeybindText.fontSize * 0.5f; + KeybindText.enableAutoSizing = true; + Button.usesRemainingSprite.transform.localPosition = new Vector3(-0.341f, 0.45f, -0.1f); + UpdateKeybindIcon(); + } + + private void OnKeybind() + { + if (Button == null || !Enabled(PlayerControl.LocalPlayer.Data.Role)) return; + + Button.GetComponent().OnClick.Invoke(); + } + + internal void UpdateKeybindIcon() + { + if (Keybind == null || KeybindText == null) return; + + var text = ActiveInputManager.currentControlType switch + { + ActiveInputManager.InputType.Keyboard when Keybind.CurrentKey != KeyboardKeyCode.None => Keybind.CurrentKey.ToString(), + ActiveInputManager.InputType.Joystick => Keybind.CurrentControllerButton, + _ => string.Empty + }; + + KeybindText.text = text; + KeybindIcon?.SetActive(text.Length > 0 && LocalSettingsTabSingleton.Instance.ShowKeybinds.Value); } /// @@ -262,22 +231,13 @@ public abstract class CustomActionButton /// Whether the button's position should change in-game. public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true) { - if (!HudManager.InstanceExists || Button == null) - { - return; - } + if (!HudManager.InstanceExists || Button == null) return; Location = location; - if (!moveButton) - { - return; - } + if (!moveButton) return; - if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null) - { - return; - } + if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null) return; switch (location) { @@ -305,10 +265,7 @@ public abstract class CustomActionButton public virtual void ResetCooldownAndOrEffect() { Timer = Cooldown; - if (EffectActive) - { - OnEffectEnd(); - } + if (EffectActive) OnEffectEnd(); EffectActive = false; } @@ -328,10 +285,7 @@ public abstract class CustomActionButton /// The new to override with. public virtual void OverrideSprite(Sprite sprite) { - if (Button != null) - { - Button.graphic.sprite = sprite; - } + if (Button != null) Button.graphic.sprite = sprite; } /// @@ -388,10 +342,7 @@ public abstract class CustomActionButton UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue); Button?.SetUsesRemaining(UsesLeft); - if (Button != null) - { - Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white; - } + if (Button != null) Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white; } /// @@ -469,8 +420,7 @@ public abstract class CustomActionButton /// A value that represents whether the button should light up or not. public virtual bool CanUse() { - return PlayerControl.LocalPlayer.moveable && - ((EffectActive && IsEffectCancellable()) || (!EffectActive && (!LimitedUses || UsesLeft > 0))); + return PlayerControl.LocalPlayer.moveable && ((EffectActive && IsEffectCancellable()) || (!EffectActive && (!LimitedUses || UsesLeft > 0))); } /// @@ -498,10 +448,7 @@ public abstract class CustomActionButton return; } - if (!CanClick()) - { - return; - } + if (!CanClick()) return; if (LimitedUses) { @@ -530,20 +477,9 @@ public abstract class CustomActionButton /// The local PlayerControl. public virtual void FixedUpdateHandler(PlayerControl playerControl) { - if (Keybind != null && KeybindText != null) - { - KeybindText.text = Keybind.CurrentKey.ToString(); - KeybindIcon?.SetActive(ActiveInputManager.currentControlType is ActiveInputManager.InputType.Keyboard && - LocalSettingsTabSingleton.Instance.ShowKeybinds.Value && - Keybind.CurrentKey != KeyboardKeyCode.None); - } - if (Timer >= 0) { - if (!TimerPaused && (!PauseTimerInVent || !playerControl.inVent)) - { - Timer -= Time.deltaTime; - } + if (!TimerPaused && (!PauseTimerInVent || !playerControl.inVent)) Timer -= Time.deltaTime; } else if (HasEffect && EffectActive && EffectDuration > 0) { @@ -555,13 +491,9 @@ public abstract class CustomActionButton if (Button) { if (CanUse()) - { Button!.SetEnabled(); - } else - { Button!.SetDisabled(); - } if (EffectActive && EffectDuration > 0) { @@ -584,8 +516,7 @@ public abstract class CustomActionButton /// Custom action button that has a target . /// /// The type of the target object. -public abstract class CustomActionButton : CustomActionButton - where T : MonoBehaviour +public abstract class CustomActionButton : CustomActionButton where T : MonoBehaviour { /// /// Gets or sets the target of the button. @@ -623,10 +554,7 @@ public abstract class CustomActionButton : CustomActionButton public override bool CanUse() { var newTarget = GetTarget(); - if (newTarget != Target) - { - SetOutline(false); - } + if (newTarget != Target) SetOutline(false); Target = IsTargetValid(newTarget) ? newTarget : null; SetOutline(true); @@ -648,4 +576,4 @@ public abstract class CustomActionButton : CustomActionButton SetOutline(false); Target = null; } -} +} \ No newline at end of file diff --git a/MiraAPI/Keybinds/BaseKeybind.cs b/MiraAPI/Keybinds/BaseKeybind.cs index 95de3c8..c853c1d 100644 --- a/MiraAPI/Keybinds/BaseKeybind.cs +++ b/MiraAPI/Keybinds/BaseKeybind.cs @@ -1,4 +1,4 @@ -using System; +using System; using Rewired; namespace MiraAPI.Keybinds; @@ -23,6 +23,11 @@ public class BaseKeybind /// public KeyboardKeyCode CurrentKey => KeybindUtils.GetKeycodeByKeybind(this); + /// + /// Gets the display label of the currently assigned button on the active controller. + /// + public string CurrentControllerButton => KeybindUtils.GetControllerButtonByKeybind(this); + /// /// Gets or sets the handler of the keybind. Invoked when the keybind is activated. /// @@ -74,4 +79,4 @@ public class BaseKeybind Handler -= action; #pragma warning restore CS8601 // Possible null reference assignment } -} +} \ No newline at end of file diff --git a/MiraAPI/Keybinds/ControllerButton.cs b/MiraAPI/Keybinds/ControllerButton.cs new file mode 100644 index 0000000..c00eb4b --- /dev/null +++ b/MiraAPI/Keybinds/ControllerButton.cs @@ -0,0 +1,87 @@ +namespace MiraAPI.Keybinds; + +/// +/// Controller buttons used for default keybinds. +/// +public enum ControllerButton +{ + /// + /// The bottom face button (Cross on PlayStation, A on Xbox). + /// + South = 4, + + /// + /// The right face button (Circle on PlayStation, B on Xbox). + /// + East = 5, + + /// + /// The left face button (Square on PlayStation, X on Xbox). + /// + West = 7, + + /// + /// The top face button (Triangle on PlayStation, Y on Xbox). + /// + North = 8, + + /// + /// The left bumper (L1 on PlayStation, LB on Xbox). + /// + LeftBumper = 10, + + /// + /// The right bumper (R1 on PlayStation, RB on Xbox). + /// + RightBumper = 12, + + /// + /// The left trigger (L2 on PlayStation, LT on Xbox). + /// + LeftTrigger = 11, + + /// + /// The right trigger (R2 on PlayStation, RT on Xbox). + /// + RightTrigger = 13, + + /// + /// The left stick button (L3 on PlayStation). + /// + LeftStick = 17, + + /// + /// The right stick button (R3 on PlayStation). + /// + RightStick = 18, + + /// + /// The back button (Create on PlayStation, View on Xbox). + /// + Back = 14, + + /// + /// The start button (Options on PlayStation, Menu on Xbox). + /// + Start = 15, + + /// + /// The D-pad up button. + /// + DPadUp = 19, + + /// + /// The D-pad right button. + /// + DPadRight = 20, + + /// + /// The D-pad down button. + /// + DPadDown = 21, + + /// + /// The D-pad left button. + /// + DPadLeft = 22, +} diff --git a/MiraAPI/Keybinds/KeybindManager.cs b/MiraAPI/Keybinds/KeybindManager.cs index 055d2ca..7d7e3c8 100644 --- a/MiraAPI/Keybinds/KeybindManager.cs +++ b/MiraAPI/Keybinds/KeybindManager.cs @@ -17,8 +17,7 @@ public static class KeybindManager /// public static List Keybinds { get; } = []; - [HideFromIl2Cpp] - internal static Dictionary VanillaKeybinds { get; set; } = []; + [HideFromIl2Cpp] internal static Dictionary VanillaKeybinds { get; set; } = []; internal static void RewiredInit() { @@ -33,12 +32,40 @@ public static class KeybindManager continue; } - keybind.RewiredInputAction = instance.userData.RegisterModBind( - keybind.Id, - keybind.Name, - keybind.SourcePluginName, - keybind.DefaultKey, - modifiers: keybind.ModifierKeys); + keybind.RewiredInputAction = instance.userData.RegisterModBind(keybind.Id, keybind.Name, keybind.SourcePluginName, keybind.DefaultKey, modifiers: keybind.ModifierKeys); + + if (keybind.DefaultControllerButton == null) continue; + + var button = (int)keybind.DefaultControllerButton.Value; + var template = instance.dataFiles.GetJoystickTemplate(GamepadTemplate.typeGuid); + foreach (var map in instance.userData.joystickMaps) + { + if (map.categoryId != 1) continue; + + var elementId = button; + var elementType = keybind.DefaultControllerButton is ControllerButton.LeftTrigger or ControllerButton.RightTrigger ? ControllerElementType.Axis : ControllerElementType.Button; + + if (map.hardwareGuid != GamepadTemplate.typeGuid) + { + var controller = template?.joysticks.ToArray().FirstOrDefault(x => x.JoystickGuid == map.hardwareGuid); + elementId = controller?.GetJoystickElementId(button) ?? -1; + if (elementId < 0) continue; + + var element = instance.dataFiles.GetHardwareJoystickMap(map.hardwareGuid)?.GetElementIdentifier(elementId); + if (element == null) continue; + + elementType = element.elementType; + } + + map.actionElementMaps.Add(new ActionElementMap + { + _actionId = keybind.RewiredInputAction.id, + _elementIdentifierId = elementId, + _elementType = elementType, + _axisRange = AxisRange.Positive, + _axisContribution = Pole.Positive + }); + } } } catch (Exception e) @@ -50,24 +77,66 @@ public static class KeybindManager /// /// Returns all conflicts where s use the same key. /// - /// A list of pairs of keybinds that conflict. + /// The conflicting keybinds grouped by keycode. public static Dictionary> GetConflicts() { var conflicts = new Dictionary>(); foreach (var keybind in Keybinds) { - var all = GetKeybindsForKey(keybind.CurrentKey, true); + var key = keybind.CurrentKey; + if (key == KeyboardKeyCode.None) continue; + + var all = GetKeybindsForKey(key, true); if (all.Length > 1) { - if (!conflicts.TryGetValue(keybind.CurrentKey, out var group)) + if (!conflicts.TryGetValue(key, out var group)) { - conflicts.Add(keybind.CurrentKey, []); - group = conflicts[keybind.CurrentKey]; + group = []; + conflicts.Add(key, group); } - if (!group.Contains(keybind)) + if (!group.Contains(keybind)) group.Add(keybind); + } + } + + return conflicts; + } + + /// + /// Returns controller bindings shared by exclusive custom keybinds. + /// + /// Whether to check only active controller maps and available HUD buttons. + /// The conflicting custom bindings. + public static List GetControllerConflicts(bool enabledOnly = false) + { + var conflicts = new List(); + var actions = Keybinds.Where(x => x.Exclusive && x.RewiredInputAction != null && (!enabledOnly || KeybindUtils.IsControllerActionActive(x.RewiredInputAction.id))).Select(x => x.RewiredInputAction!.id).ToHashSet(); + var player = ReInput.players.GetPlayer(0); + for (var i = 0; i < player.controllers.joystickCount; i++) + { + var controller = player.controllers.Joysticks[i]; + var maps = new CppCollections.List(player.controllers.maps.GetMaps(controller).Cast>()).ToArray(); + foreach (var map in maps) + { + if (enabledOnly && !map.enabled) continue; + + foreach (var binding in map.GetElementMaps()) { - group.Add(keybind); + if (!binding.enabled || !actions.Contains(binding.actionId)) continue; + + foreach (var otherMap in maps) + { + if (enabledOnly && !otherMap.enabled) continue; + if (!enabledOnly && map.categoryId != otherMap.categoryId && map.categoryId != 0 && otherMap.categoryId != 0) continue; + + foreach (var other in otherMap.GetElementMaps()) + { + if (!other.enabled || binding.actionId == other.actionId || !actions.Contains(other.actionId) || !binding.CheckForAssignmentConflict(other)) continue; + + if (!conflicts.Contains(binding)) conflicts.Add(binding); + if (!conflicts.Contains(other)) conflicts.Add(other); + } + } } } } @@ -79,19 +148,15 @@ public static class KeybindManager /// Returns all s for a specified keycode. /// /// The key to look for. - /// /// Should only exclusive keybinds be present. + /// Whether to return only exclusive keybinds. /// The list of s. public static MiraKeybind[] GetKeybindsForKey(KeyboardKeyCode keyCode, bool exclusiveCheck = false) { var all = new List(); foreach (var keybind in Keybinds) - { if (keybind.CurrentKey == keyCode && (keybind.Exclusive || !exclusiveCheck)) - { all.Add(keybind); - } - } return [.. all]; } -} +} \ No newline at end of file diff --git a/MiraAPI/Keybinds/KeybindUtils.cs b/MiraAPI/Keybinds/KeybindUtils.cs index 3b8502b..27bb421 100644 --- a/MiraAPI/Keybinds/KeybindUtils.cs +++ b/MiraAPI/Keybinds/KeybindUtils.cs @@ -1,10 +1,12 @@ using System; +using System.Linq; +using MiraAPI.Hud; using Rewired; namespace MiraAPI.Keybinds; /// -/// Collection of keybind-related utility classes. +/// Collection of keybind-related utilities. /// public static class KeybindUtils { @@ -23,6 +25,25 @@ public static class KeybindUtils return keybind.RewiredInputAction == null ? KeyboardKeyCode.None : GetKeycodeByActionId(keybind.RewiredInputAction.id); } + /// + /// Returns the display label for the currently assigned controller button of a . + /// + /// The keybind to get the controller button from. + /// The button label, or an empty string if no controller binding is available. + public static string GetControllerButtonByKeybind(BaseKeybind keybind) + { + var map = keybind.RewiredInputAction == null ? null : GetControllerElementMap(keybind.RewiredInputAction.id); + var name = map?.elementIdentifierName ?? string.Empty; + return name.ToLowerInvariant() switch + { + "cross" => "X", + "square" => "□", + "circle" => "○", + "triangle" => "△", + _ => name + }; + } + /// /// Gets the keycode for an action with . /// @@ -37,23 +58,41 @@ public static class KeybindUtils /// Gets a by id with . /// /// The action ID. - /// The keyboard keycode. + /// The keyboard binding, or null if the action is unassigned. public static ActionElementMap? GetActionElementMap(int actionId) { var player = ReInput.players.GetPlayer(0); return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false); } + /// + /// Gets the binding for an action on the active controller. + /// + /// The action ID. + /// The controller binding, or null if no active controller or binding is available. + public static ActionElementMap? GetControllerElementMap(int actionId) + { + var player = ReInput.players.GetPlayer(0); + var controller = player.controllers.GetLastActiveController(ControllerType.Joystick); + return controller == null ? null : player.controllers.maps.GetFirstElementMapWithAction(controller, actionId, false); + } + /// /// Gets an by id. /// - /// The action identifier name. - /// The keyboard keycode. + /// The action ID. + /// The input action, or null if it is unavailable. public static InputAction? GetInputActionById(int id) { return RewiredInputManager?.userData?.GetActionById(id); } + internal static bool IsControllerActionActive(int actionId) + { + var buttons = CustomButtonManager.Buttons.Where(x => x.Keybind?.RewiredInputAction?.id == actionId).ToArray(); + return buttons.Length == 0 || buttons.Any(x => x.Button != null && x.Button.isActiveAndEnabled && x.Enabled(PlayerControl.LocalPlayer?.Data?.Role)); + } + /// /// Finds and returns an unused KeyCode that is not equal to the excluded key. /// @@ -73,4 +112,4 @@ public static class KeybindUtils return KeyboardKeyCode.None; } -} +} \ No newline at end of file diff --git a/MiraAPI/Keybinds/MiraGlobalKeybinds.cs b/MiraAPI/Keybinds/MiraGlobalKeybinds.cs index 7e4b21b..be1dc84 100644 --- a/MiraAPI/Keybinds/MiraGlobalKeybinds.cs +++ b/MiraAPI/Keybinds/MiraGlobalKeybinds.cs @@ -1,4 +1,4 @@ -using Rewired; +using Rewired; namespace MiraAPI.Keybinds; @@ -12,12 +12,12 @@ public static class MiraGlobalKeybinds /// /// Gets the keybind for primary abilities. /// - public static MiraKeybind PrimaryAbility { get; } = new("Primary Ability", KeyboardKeyCode.T); + public static MiraKeybind PrimaryAbility { get; } = new("Primary Ability", KeyboardKeyCode.T, ControllerButton.RightTrigger); /// /// Gets the keybind for secondary abilities. /// - public static MiraKeybind SecondaryAbility { get; } = new("Secondary Ability", KeyboardKeyCode.Y); + public static MiraKeybind SecondaryAbility { get; } = new("Secondary Ability", KeyboardKeyCode.Y, ControllerButton.RightBumper); /// /// Gets the keybind for Tertiary abilities. @@ -27,7 +27,7 @@ public static class MiraGlobalKeybinds /// /// Gets the keybind for primary modifier abilities. /// - public static MiraKeybind ModifierPrimaryAbility { get; } = new("Modifier Primary Ability", KeyboardKeyCode.I); + public static MiraKeybind ModifierPrimaryAbility { get; } = new("Modifier Primary Ability", KeyboardKeyCode.I, ControllerButton.LeftTrigger); /// /// Gets the keybind for secondary modifier abilities. @@ -38,4 +38,4 @@ public static class MiraGlobalKeybinds /// Gets the keybind for Tertiary modifier abilities. /// public static MiraKeybind ModifierTertiaryAbility { get; } = new("Modifier Tertiary Ability", KeyboardKeyCode.P); -} +} \ No newline at end of file diff --git a/MiraAPI/Keybinds/MiraKeybind.cs b/MiraAPI/Keybinds/MiraKeybind.cs index 5eca155..ac6147b 100644 --- a/MiraAPI/Keybinds/MiraKeybind.cs +++ b/MiraAPI/Keybinds/MiraKeybind.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using Rewired; namespace MiraAPI.Keybinds; @@ -10,11 +10,7 @@ namespace MiraAPI.Keybinds; /// The default keycode. /// Up to 3 optional s. /// Is exclusive. -public class MiraKeybind( - string name, - KeyboardKeyCode? defaultKeycode, - ModifierKey[]? modifierKeys = null, - bool exclusive = true) : BaseKeybind(name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_')) +public class MiraKeybind(string name, KeyboardKeyCode? defaultKeycode, ModifierKey[]? modifierKeys = null, bool exclusive = true) : BaseKeybind(name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_')) { /// /// Gets name of the keybind. @@ -26,6 +22,11 @@ public class MiraKeybind( /// public KeyboardKeyCode DefaultKey { get; } = defaultKeycode ?? KeyboardKeyCode.None; + /// + /// Gets the default controller button. If , the controller binding is left unassigned. + /// + public ControllerButton? DefaultControllerButton { get; } + /// /// Gets the s assigned to this keybind. /// Due to Rewired limitations, there can only be 3 s. @@ -34,14 +35,28 @@ public class MiraKeybind( /// /// Gets a value indicating whether this keybind should be checked for conflicts with other exclusive keybinds using the same key. + /// Active exclusive controller bindings take priority over vanilla actions during gameplay. /// public bool Exclusive { get; } = exclusive; internal string? SourcePluginName { get; set; } + /// + /// Initializes a new instance of the class with a controller binding. + /// + /// The name of the keybind. + /// The default keycode. + /// The default controller button. + /// Up to 3 optional s for the keyboard binding. + /// Is exclusive. + public MiraKeybind(string name, KeyboardKeyCode? defaultKeycode, ControllerButton defaultControllerButton, ModifierKey[]? modifierKeys = null, bool exclusive = true) : this(name, defaultKeycode, modifierKeys, exclusive) + { + DefaultControllerButton = defaultControllerButton; + } + /// public override string ToString() { return $"{Name} ({Id})"; } -} +} \ No newline at end of file diff --git a/MiraAPI/Keybinds/RegisterCustomKeybindsAttribute.cs b/MiraAPI/Keybinds/RegisterCustomKeybindsAttribute.cs index 088f158..77e0735 100644 --- a/MiraAPI/Keybinds/RegisterCustomKeybindsAttribute.cs +++ b/MiraAPI/Keybinds/RegisterCustomKeybindsAttribute.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace MiraAPI.Keybinds; @@ -6,4 +6,4 @@ namespace MiraAPI.Keybinds; /// Used to mark a class for s registration. /// [AttributeUsage(AttributeTargets.Class)] -public class RegisterCustomKeybindsAttribute : Attribute; +public class RegisterCustomKeybindsAttribute : Attribute; \ No newline at end of file diff --git a/MiraAPI/Keybinds/VanillaKeybind.cs b/MiraAPI/Keybinds/VanillaKeybind.cs index a6b7350..9c23b3a 100644 --- a/MiraAPI/Keybinds/VanillaKeybind.cs +++ b/MiraAPI/Keybinds/VanillaKeybind.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; namespace MiraAPI.Keybinds; @@ -21,4 +21,4 @@ public class VanillaKeybind : BaseKeybind RewiredInputAction = KeybindUtils.GetInputActionById(id); Id = RewiredInputAction != null ? RewiredInputAction.name : id.ToString(CultureInfo.InvariantCulture); } -} +} \ No newline at end of file diff --git a/MiraAPI/Keybinds/VanillaKeybindSingleton.cs b/MiraAPI/Keybinds/VanillaKeybindSingleton.cs index 0754756..ff30d9d 100644 --- a/MiraAPI/Keybinds/VanillaKeybindSingleton.cs +++ b/MiraAPI/Keybinds/VanillaKeybindSingleton.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace MiraAPI.Keybinds; @@ -6,8 +6,7 @@ namespace MiraAPI.Keybinds; /// Singleton for getting a by . /// /// type. -public static class VanillaKeybinding - where T : ActionButton +public static class VanillaKeybinding where T : ActionButton { [SuppressMessage("Major Code Smell", "S2743:Static fields should not be used in generic types", Justification = "The instance holds a reference to the button that defines the class's type parameter.")] private static VanillaKeybind? _instance; @@ -17,4 +16,4 @@ public static class VanillaKeybinding /// [SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a utility class to get the instance of a vanilla keybind.")] public static VanillaKeybind Instance => _instance ??= KeybindManager.VanillaKeybinds[typeof(T)]; -} +} \ No newline at end of file diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index 5c3038b..3ccb81b 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -1,6 +1,6 @@ using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; +using System.Linq; using HarmonyLib; using MiraAPI.Hud; using MiraAPI.Keybinds; @@ -28,60 +28,37 @@ public static class HudManagerPatches private static Dictionary vanillaKeybindIcons = []; - [SuppressMessage("Critical Code Smell", "S2223:Non-constant static fields should not be visible", Justification = "This is internal and will never be used by user code.")] - [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "This is internal and will never be used by user code.")] - internal static List ModdedKeybindIcons = []; - public static IEnumerator CoResizeUI() { - while (!HudManager.Instance) - { - yield return null; - } + while (!HudManager.Instance) yield return null; yield return new WaitForSeconds(0.01f); ResizeUI(LocalSettingsTabSingleton.Instance.ButtonUIFactorSlider.Value); } private static Transform _storedButtonsParent; + public static void ResizeUI(float scaleFactor) { - if (!_storedButtonsParent) - { - _storedButtonsParent = HudManager.Instance.transform.FindChild("Buttons"); - } + if (!_storedButtonsParent) _storedButtonsParent = HudManager.Instance.transform.FindChild("Buttons"); if (_storedButtonsParent) - { foreach (var aspect in _storedButtonsParent.GetComponentsInChildren(true)) { - if (!aspect.gameObject) - { - continue; - } + if (!aspect.gameObject) continue; - if (aspect.gameObject.transform.parent.name == "TopRight") - { - continue; - } + if (aspect.gameObject.transform.parent.name == "TopRight") continue; - if (aspect.gameObject.name.Contains("TopRight")) - { - continue; - } + if (aspect.gameObject.name.Contains("TopRight")) continue; aspect.gameObject.SetActive(!aspect.isActiveAndEnabled); aspect.DistanceFromEdge *= new Vector2(scaleFactor, scaleFactor); aspect.gameObject.SetActive(!aspect.isActiveAndEnabled); } - } foreach (var button in HudManager.Instance.GetComponentsInChildren(true)) { - if (!button.gameObject) - { - continue; - } + if (!button.gameObject) continue; button.gameObject.SetActive(!button.isActiveAndEnabled); button.gameObject.transform.localScale *= scaleFactor; @@ -91,15 +68,9 @@ public static class HudManagerPatches if (!_storedButtonsParent) return; foreach (var arrange in _storedButtonsParent.GetComponentsInChildren(true)) { - if (!arrange.gameObject || !arrange.transform) - { - continue; - } + if (!arrange.gameObject || !arrange.transform) continue; - if (arrange.gameObject.name.Contains("TopRight")) - { - continue; - } + if (arrange.gameObject.name.Contains("TopRight")) continue; arrange.gameObject.SetActive(!arrange.isActiveAndEnabled); arrange.CellSize = new Vector2(scaleFactor, scaleFactor); @@ -143,10 +114,7 @@ public static class HudManagerPatches [HarmonyPatch(typeof(ChatController), nameof(ChatController.Close))] public static void TogglePrefix(ChatController __instance) { - if (!MiraHudHelper.ClonedChatButton) - { - return; - } + if (!MiraHudHelper.ClonedChatButton) return; __instance.chatButton.transform.localPosition = MiraHudHelper.ClonedChatButton.transform.localPosition + new Vector3(-0.3f, 0); } @@ -179,25 +147,13 @@ public static class HudManagerPatches public static void StartPostfix(HudManager __instance) { __instance.gameObject.AddComponent(); - if (Buttons == null) - { - Buttons = __instance.transform.Find("Buttons"); - } + if (Buttons == null) Buttons = __instance.transform.Find("Buttons"); - if (BottomRight == null) - { - BottomRight = Buttons.Find("BottomRight"); - } + if (BottomRight == null) BottomRight = Buttons.Find("BottomRight"); - if (BottomLeft == null) - { - BottomLeft = Object.Instantiate(BottomRight.gameObject, Buttons); - } + if (BottomLeft == null) BottomLeft = Object.Instantiate(BottomRight.gameObject, Buttons); - foreach (var t in BottomLeft.GetComponentsInChildren(true)) - { - t.gameObject.Destroy(); - } + foreach (var t in BottomLeft.GetComponentsInChildren(true)) t.gameObject.Destroy(); var gridArrange = BottomLeft.GetComponent(); var aspectPosition = BottomLeft.GetComponent(); @@ -209,10 +165,7 @@ public static class HudManagerPatches if (Constants.GetPlatformType() is Platforms.Android or Platforms.IPhone) { var fakeButton = Object.Instantiate(__instance.AbilityButton, BottomLeft.transform); - foreach (var renderer in fakeButton.GetComponentsInChildren()) - { - renderer.enabled = false; - } + foreach (var renderer in fakeButton.GetComponentsInChildren()) renderer.enabled = false; fakeButton.buttonLabelText.Destroy(); fakeButton.cooldownTimerText.Destroy(); @@ -222,21 +175,16 @@ public static class HudManagerPatches fakeButton.Destroy(); } - ModdedKeybindIcons = []; - foreach (var button in CustomButtonManager.CustomButtons) { var location = button.Location switch { ButtonLocation.BottomLeft => BottomLeft.transform, ButtonLocation.BottomRight => BottomRight, - _ => null, + _ => null }; - if (location is null) - { - continue; - } + if (location is null) continue; try { @@ -264,7 +212,7 @@ public static class HudManagerPatches { __instance.ReportButton.gameObject, 7 }, { __instance.ImpostorVentButton.gameObject, 50 }, { __instance.SabotageButton.gameObject, 4 }, - { __instance.AbilityButton.gameObject, 49 }, + { __instance.AbilityButton.gameObject, 49 } }; foreach (var kvp in vanillaButtons) @@ -272,25 +220,21 @@ public static class HudManagerPatches var buttonObj = kvp.Key; var actionId = kvp.Value; - var key = KeybindUtils.GetKeycodeByActionId(actionId); - if (key == KeyboardKeyCode.None) - { - continue; - } - - var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos); - vanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent(), actionId); if (!MiraApiPlugin.IsMobile) { var comp = buttonObj.GetComponent(); KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp; } + + var key = KeybindUtils.GetKeycodeByActionId(actionId); + if (key == KeyboardKeyCode.None) continue; + + var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos); + vanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent(), actionId); } - MiraApiSettings.OldButtonScaleFactor = - LocalSettingsTabSingleton.Instance.ButtonUIFactorSlider.Value; - MiraApiSettings.OldUiButtonScaleFactor = - LocalSettingsTabSingleton.Instance.TopRightButtonsFactorSlider.Value; + MiraApiSettings.OldButtonScaleFactor = LocalSettingsTabSingleton.Instance.ButtonUIFactorSlider.Value; + MiraApiSettings.OldUiButtonScaleFactor = LocalSettingsTabSingleton.Instance.TopRightButtonsFactorSlider.Value; Coroutines.Start(CoResizeUI()); } @@ -306,13 +250,9 @@ public static class HudManagerPatches public static void SetHudActivePostfix(HudManager __instance, PlayerControl localPlayer, RoleBehaviour role, bool isActive) { __instance.AdminButton.ToggleVisible(isActive && role.IsImpostor && GameOptionsManager.Instance.CurrentGameOptions.GameMode == AmongUs.GameOptions.GameModes.HideNSeek); - if (localPlayer.Data == null) - { - return; - } + if (localPlayer.Data == null) return; foreach (var button in CustomButtonManager.CustomButtons) - { try { button.SetActive(isActive, role); @@ -321,15 +261,13 @@ public static class HudManagerPatches { Error($"Failed to set custom button {button.GetType().Name} active: {e}"); } - } } [HarmonyPatch(nameof(HudManager.Update))] [HarmonyPostfix] public static void UpdatePostfix() { - var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard && - LocalSettingsTabSingleton.Instance.ShowKeybinds.Value; + var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard && LocalSettingsTabSingleton.Instance.ShowKeybinds.Value; foreach (var btnIcon in vanillaKeybindIcons) { @@ -337,44 +275,15 @@ public static class HudManagerPatches btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds); } - foreach (var btnIcon in ModdedKeybindIcons) - { - btnIcon.transform.parent.gameObject.SetActive(canSeeBinds); - } + foreach (var button in CustomButtonManager.CustomButtons) button.UpdateKeybindIcon(); var player = ReInput.players.GetPlayer(0); - var keyboard = player.controllers.Keyboard; foreach (var entry in KeybindManager.Keybinds) - { - var modKeys = entry.ModifierKeys; - bool modifierKeysPressed; - if (modKeys.Length == 0) - { - modifierKeysPressed = true; - } - else - { - modifierKeysPressed = true; - foreach (var key in modKeys) - { - if (keyboard.GetModifierKey(key)) continue; - modifierKeysPressed = false; - break; - } - } - - if (player.GetButtonDown(entry.Id) && modifierKeysPressed) - { + if (entry.RewiredInputAction != null && player.GetButtonDown(entry.RewiredInputAction.id)) entry.Invoke(); - } - } foreach (var entry in KeybindManager.VanillaKeybinds.Values) - { if (player.GetButtonDown(entry.Id)) - { entry.Invoke(); - } - } } -} +} \ No newline at end of file diff --git a/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs b/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs index 6666a2c..dc112d4 100644 --- a/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs +++ b/MiraAPI/Patches/Keybinds/ControlMapperPatches.cs @@ -1,6 +1,7 @@ -using System.Linq; +using System.Linq; using HarmonyLib; using MiraAPI.Keybinds; +using Rewired; using Rewired.UI.ControlMapper; using UnityEngine; using UnityEngine.UI; @@ -10,54 +11,72 @@ namespace MiraAPI.Patches.Keybinds; [HarmonyPatch(typeof(ControlMapper))] public static class ControlMapperPatches { - // Patching this crashes for Epic Games users - /*[HarmonyPrefix] - [HarmonyPatch(nameof(ControlMapper.Start))] - private static void StartPrefix(ControlMapper __instance) + [HarmonyPrefix] + [HarmonyPatch(nameof(ControlMapper.Initialize))] + private static void InitializePrefix(ControlMapper __instance) { - var doneButton = GameObject.Find("DoneButton")?.GetComponent(); - if (doneButton == null) - { - return; - } + __instance.showControllers = true; + __instance.showGlyphs = false; + } - var toggle = Object.Instantiate(doneButton, doneButton.transform.parent); - var text = toggle.GetComponentInChildren(); - var entry = LocalSettingsTabSingleton.Instance.ShowKeybinds; - toggle.gameObject.name = "KeybindsVisibleToggle"; - text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}"; - toggle.onClick.RemoveAllListeners(); - toggle.GetComponent().identifier = "KeybindsVisibleToggle"; - toggle.onClick.AddListener((UnityAction)(() => - { - entry.Value = !entry.Value; - text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}"; - })); - }*/ + [HarmonyPostfix] + [HarmonyPatch(nameof(ControlMapper.CreateLayout))] + private static void CreateLayoutPostfix(ControlMapper __instance) + { + __instance.references.controllerGroup.gameObject.SetActive(false); + __instance.references.assignedControllersGroup.gameObject.SetActive(false); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ControlMapper.GetControllerMap))] + private static bool GetControllerMapPrefix(ControlMapper __instance, ControllerType type, ref ControllerMap? __result) + { + if (type != ControllerType.Joystick) return true; + + __result = __instance.currentPlayer?.controllers.maps.GetFirstMapInCategory(type, __instance.currentJoystickId, 1); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ControlMapper.HasElementAssignmentConflicts))] + private static bool HasElementAssignmentConflictsPrefix(ControlMapper.InputMapping mapping, ref bool __result) + { + if (mapping == null || mapping.controllerType != ControllerType.Joystick || !KeybindManager.Keybinds.Any(x => x.RewiredInputAction?.id == mapping.fieldInfo.actionId)) return true; + + __result = false; + return false; + } [HarmonyPrefix] [HarmonyPatch(nameof(ControlMapper.Update))] private static void UpdatePrefix(ControlMapper __instance) { + var conflicts = KeybindManager.GetConflicts(); + var controllerConflicts = KeybindManager.GetControllerConflicts(); foreach (var element in __instance.themedElements) { var info = element.GetComponent(); - if (info == null) - { - continue; - } + if (info == null) continue; - var key = info.glyphOrText?.actionElementMap?.keyboardKeyCode; - if (key == null) - { - continue; - } + var map = ReInput.mapping.GetActionElementMap(info.actionElementMapId); + var image = element.GetComponent(); + if (image == null) continue; - var conflicts = KeybindManager.GetConflicts(); - if (conflicts.Any(x => x.Key.ToString() == key.ToString())) + var hasConflict = map?.controllerMap.controllerType switch { - element.GetComponent().color = Color.red; + ControllerType.Keyboard => conflicts.ContainsKey(map.keyboardKeyCode), + ControllerType.Joystick => controllerConflicts.Any(x => x.controllerMap.id == map.controllerMap.id && x.id == map.id), + _ => false + }; + if (hasConflict) + { + image.color = Color.red; + } + else if (image.color == Color.red) + { + image.color = Color.white; + element.ApplyTheme(); } } } -} +} \ No newline at end of file diff --git a/MiraAPI/Patches/Keybinds/KeybindsPatch.cs b/MiraAPI/Patches/Keybinds/KeybindsPatch.cs index 9c1034f..a3fd98b 100644 --- a/MiraAPI/Patches/Keybinds/KeybindsPatch.cs +++ b/MiraAPI/Patches/Keybinds/KeybindsPatch.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using HarmonyLib; using MiraAPI.Keybinds; using Rewired; @@ -10,26 +12,25 @@ public static class KeybindMenuPatch private static bool _registered; [HarmonyPrefix] + + // ReSharper disable once InconsistentNaming private static void AwakePrefix(InputManager_Base __instance) { - if (_registered) - { - return; - } + if (_registered) return; KeybindUtils.RewiredInputManager = __instance; KeybindManager.RewiredInit(); _registered = true; // Set these here right after input manager initializes - KeybindManager.VanillaKeybinds = new() + KeybindManager.VanillaKeybinds = new Dictionary { { typeof(KillButton), new VanillaKeybind(8) }, { typeof(UseButton), new VanillaKeybind(6) }, { typeof(ReportButton), new VanillaKeybind(7) }, { typeof(VentButton), new VanillaKeybind(50) }, { typeof(SabotageButton), new VanillaKeybind(4) }, - { typeof(AbilityButton), new VanillaKeybind(49) }, + { typeof(AbilityButton), new VanillaKeybind(49) } }; } -} +} \ No newline at end of file diff --git a/MiraAPI/Patches/Keybinds/OptionsMenuBehaviourPatch.cs b/MiraAPI/Patches/Keybinds/OptionsMenuBehaviourPatch.cs index 584d6cc..e2a1be1 100644 --- a/MiraAPI/Patches/Keybinds/OptionsMenuBehaviourPatch.cs +++ b/MiraAPI/Patches/Keybinds/OptionsMenuBehaviourPatch.cs @@ -1,4 +1,4 @@ -using HarmonyLib; +using HarmonyLib; using MiraAPI.Keybinds; using UnityEngine; @@ -10,7 +10,7 @@ public static class OptionsMenuBehaviourPatch private static ButtonRolloverHandler? _remapRollover; private static SpriteRenderer? _remapBackground; - private static bool Conflicts => KeybindManager.GetConflicts().Count > 0; + private static bool Conflicts => KeybindManager.GetConflicts().Count > 0 || KeybindManager.GetControllerConflicts().Count > 0; [HarmonyPostfix] [HarmonyPatch(nameof(OptionsMenuBehaviour.Open))] @@ -32,18 +32,14 @@ public static class OptionsMenuBehaviourPatch [HarmonyPatch(nameof(OptionsMenuBehaviour.Update))] private static void UpdatePostfix() { - if (_remapRollover == null) - { - return; - } + if (_remapRollover == null) return; - if (_remapBackground == null) - { - return; - } + if (_remapBackground == null) return; - _remapRollover.OutColor = Conflicts ? Color.red : Color.white; - _remapRollover.UnselectedColor = Conflicts ? Color.red : Color.white; - _remapRollover.OverColor = Conflicts ? new Color32(255, 55, 55, 255) : Palette.AcceptedGreen; + var conflicts = Conflicts; + _remapRollover.OutColor = conflicts ? Color.red : Color.white; + _remapRollover.UnselectedColor = conflicts ? Color.red : Color.white; + _remapRollover.OverColor = conflicts ? new Color32(255, 55, 55, 255) : Palette.AcceptedGreen; + if (!conflicts && _remapBackground.color == Color.red) _remapBackground.color = Color.white; } -} +} \ No newline at end of file diff --git a/MiraAPI/Patches/Keybinds/PlayerKeybindPatches.cs b/MiraAPI/Patches/Keybinds/PlayerKeybindPatches.cs new file mode 100644 index 0000000..bc9b8c4 --- /dev/null +++ b/MiraAPI/Patches/Keybinds/PlayerKeybindPatches.cs @@ -0,0 +1,64 @@ +using System.Linq; +using HarmonyLib; +using MiraAPI.Keybinds; +using Rewired; + +namespace MiraAPI.Patches.Keybinds; + +[HarmonyPatch(typeof(Player))] +public static class PlayerKeybindPatches +{ + [HarmonyPostfix] + [HarmonyPatch(nameof(Player.GetButtonDown), typeof(int))] + private static void GetButtonPostfix(Player __instance, int __0, ref bool __result) + { + if (!__result || __instance.id != 0 || PlayerControl.LocalPlayer == null || ConsoleJoystick.inputState != ConsoleJoystick.ConsoleInputState.Gameplay || ControllerManager.Instance.IsUiControllerActive) return; + + var keybind = KeybindManager.Keybinds.FirstOrDefault(x => x.RewiredInputAction?.id == __0); + if (keybind is { Exclusive: false }) return; + + var sources = new CppCollections.List(__instance.GetCurrentInputSources(__0).Cast>()).ToArray(); + if (sources.Length == 0 || sources.Any(x => x.controllerType != ControllerType.Joystick)) return; + + foreach (var source in sources) + { + var claimed = false; + foreach (var custom in KeybindManager.Keybinds) + { + if (custom == keybind) break; + if (!custom.Exclusive || custom.RewiredInputAction == null || !KeybindUtils.IsControllerActionActive(custom.RewiredInputAction.id)) continue; + + var inputs = new CppCollections.List(__instance.GetCurrentInputSources(custom.RewiredInputAction.id).Cast>()).ToArray(); + if (!inputs.Any(x => x.controllerType == ControllerType.Joystick && x.controller.id == source.controller.id && x.actionElementMap.CheckForAssignmentConflict(source.actionElementMap))) continue; + + claimed = true; + break; + } + + if (!claimed) return; + } + + __result = false; + } + + [HarmonyPostfix] + [HarmonyPatch(nameof(Player.GetButton), typeof(int))] + private static void GetButtonHeldPostfix(Player __instance, int __0, ref bool __result) + { + GetButtonPostfix(__instance, __0, ref __result); + } + + [HarmonyPostfix] + [HarmonyPatch(nameof(Player.GetButtonDown), typeof(string))] + private static void GetButtonByNamePostfix(Player __instance, string __0, ref bool __result) + { + if (__result) GetButtonPostfix(__instance, ReInput.mapping.GetActionId(__0), ref __result); + } + + [HarmonyPostfix] + [HarmonyPatch(nameof(Player.GetButton), typeof(string))] + private static void GetButtonHeldByNamePostfix(Player __instance, string __0, ref bool __result) + { + if (__result) GetButtonPostfix(__instance, ReInput.mapping.GetActionId(__0), ref __result); + } +} diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 57cd64b..7df947d 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -43,9 +43,7 @@ public static class Extensions chance = 0; } - return new NetData( - RoleId.Get(role), - BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value))); + return new NetData(RoleId.Get(role), BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value))); } /// @@ -60,20 +58,11 @@ public static class Extensions return; } - if (self.CloseSound && Constants.ShouldPlaySfx()) - { - SoundManager.Instance.PlaySound(self.CloseSound, false); - } + if (self.CloseSound && Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(self.CloseSound, false); - if (PlayerControl.LocalPlayer.Data.Role.TeamType == RoleTeamTypes.Crewmate) - { - GameManager.Instance.LogicMinigame.OnMinigameClose(); - } + if (PlayerControl.LocalPlayer.Data.Role.TeamType == RoleTeamTypes.Crewmate) GameManager.Instance.LogicMinigame.OnMinigameClose(); - if (PlayerControl.LocalPlayer) - { - PlayerControl.HideCursorTemporarily(); - } + if (PlayerControl.LocalPlayer) PlayerControl.HideCursorTemporarily(); self.amClosing = Minigame.CloseState.Closing; self.logger.Info(string.Concat("Closing minigame ", self.GetType().Name)); @@ -145,9 +134,7 @@ public static class Extensions { var list = input.ToList(); - return list.Count == 0 - ? throw new InvalidOperationException("Cannot get random element from an empty collection.") - : list[UnityEngine.Random.Range(0, list.Count)]; + return list.Count == 0 ? throw new InvalidOperationException("Cannot get random element from an empty collection.") : list[UnityEngine.Random.Range(0, list.Count)]; } /// @@ -201,10 +188,7 @@ public static class Extensions // ReSharper disable once InconsistentNaming (Justification: Acronym.) var il2cppList = new CppCollections.List(); - foreach (var item in systemList) - { - il2cppList.Add(item); - } + foreach (var item in systemList) il2cppList.Add(item); return il2cppList; } @@ -273,7 +257,6 @@ public static class Extensions // Left as is intentionally, did you guys mean to use it in DeepDestroy? [SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Not known until intent it clear.")] [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unclear, but please shut up.")] - #pragma warning disable SA1515 // Single-line comment should be preceded by blank line (Justification: ReSharper suppressions.) // ReSharper disable once UnusedParameter.Local (Justification: Read above.) // ReSharper disable once UnusedMember.Local (Justification: Read above.) @@ -333,10 +316,8 @@ public static class Extensions try { foreach (var mat in renderer.materials) - { if (mat != null) UnityEngine.Object.Destroy(mat); - } } catch { @@ -383,10 +364,7 @@ public static class Extensions /// An that contains s. public static IEnumerable GetAllChildren(this GameObject go) { - for (var i = 0; i < go.transform.childCount; i++) - { - yield return go.transform.GetChild(i).gameObject; - } + for (var i = 0; i < go.transform.childCount; i++) yield return go.transform.GetChild(i).gameObject; } /// @@ -396,10 +374,7 @@ public static class Extensions /// An that contains s. public static IEnumerable GetAllChildren(this Transform go) { - for (var i = 0; i < go.transform.childCount; i++) - { - yield return go.transform.GetChild(i).gameObject; - } + for (var i = 0; i < go.transform.childCount; i++) yield return go.transform.GetChild(i).gameObject; } /// @@ -410,17 +385,12 @@ public static class Extensions public static void SetSizeLimit(this SpriteRenderer sprite, float pixelSize) { sprite.drawMode = SpriteDrawMode.Sliced; - if (!sprite.sprite) - { - return; - } + if (!sprite.sprite) return; var spriteWidth = sprite.sprite.rect.width; var spriteHeight = sprite.sprite.rect.height; - sprite.size = spriteWidth < spriteHeight - ? new Vector2(pixelSize * spriteWidth / spriteHeight, pixelSize) - : new Vector2(pixelSize, pixelSize * spriteHeight / spriteWidth); + sprite.size = spriteWidth < spriteHeight ? new Vector2(pixelSize * spriteWidth / spriteHeight, pixelSize) : new Vector2(pixelSize, pixelSize * spriteHeight / spriteWidth); } /// @@ -430,10 +400,7 @@ public static class Extensions /// The scale for the sprite to be adjusted to. public static void SetSizeLimit(this GameObject spriteObj, float pixelSize) { - if (!spriteObj.TryGetComponent(out var sprite)) - { - return; - } + if (!spriteObj.TryGetComponent(out var sprite)) return; sprite.SetSizeLimit(pixelSize); } @@ -450,16 +417,10 @@ public static class Extensions /// A if there is one, otherwise. public static PlayerVoteData GetVoteData(this PlayerControl player) { - if (VoteDataComponents.TryGetValue(player, out var component)) - { - return component; - } + if (VoteDataComponents.TryGetValue(player, out var component)) return component; component = player.GetComponent(); - if (!component) - { - throw new InvalidOperationException("PlayerVoteData is not attached to the player."); - } + if (!component) throw new InvalidOperationException("PlayerVoteData is not attached to the player."); VoteDataComponents[player] = component; return component; @@ -476,7 +437,6 @@ public static class Extensions tie = true; var result = new KeyValuePair(byte.MaxValue, int.MinValue); foreach (var keyValuePair in self) - { if (keyValuePair.Value > result.Value) { result = keyValuePair; @@ -486,7 +446,6 @@ public static class Extensions { tie = true; } - } return result; } @@ -502,7 +461,6 @@ public static class Extensions tie = true; var result = new KeyValuePair(byte.MaxValue, int.MinValue); foreach (var keyValuePair in self) - { if (keyValuePair.Value > result.Value) { result = keyValuePair; @@ -512,7 +470,6 @@ public static class Extensions { tie = true; } - } return result; } @@ -525,12 +482,7 @@ public static class Extensions /// The best constructor. public static ConstructorInfo? GetBestConstructor(this Type type, params object[] args) { - return type.GetValidConstructors(args) - .OrderBy( - ctor => ctor.GetParameters() - .Select((p, i) => GetInheritanceDistance(args[i].GetType(), p.ParameterType)) - .Sum()) - .FirstOrDefault(); + return type.GetValidConstructors(args).OrderBy(ctor => ctor.GetParameters().Select((p, i) => GetInheritanceDistance(args[i].GetType(), p.ParameterType)).Sum()).FirstOrDefault(); } /// @@ -552,14 +504,11 @@ public static class Extensions /// A collection of valid constructors. public static IEnumerable GetValidConstructors(this Type type, params object[] args) { - return type.GetConstructors().Where( - x => - { - var parameters = x.GetParameters(); - return parameters.Length == args.Length && Array.TrueForAll( - parameters, - t => t.ParameterType.IsInstanceOfType(args[t.Position])); - }); + return type.GetConstructors().Where(x => + { + var parameters = x.GetParameters(); + return parameters.Length == args.Length && Array.TrueForAll(parameters, t => t.ParameterType.IsInstanceOfType(args[t.Position])); + }); } /// @@ -571,10 +520,7 @@ public static class Extensions /// The distance between the types. public static int GetInheritanceDistance(Type from, Type to) { - if (!from.IsAssignableFrom(to)) - { - return int.MaxValue; - } + if (!from.IsAssignableFrom(to)) return int.MaxValue; var type = from; var distance = 0; @@ -615,12 +561,7 @@ public static class Extensions /// The darkened . public static Color32 GetShadowColor(this Color32 color, byte darknessAmount) { - return - new Color32( - (byte)Mathf.Clamp(color.r - darknessAmount, 0, 255), - (byte)Mathf.Clamp(color.g - darknessAmount, 0, 255), - (byte)Mathf.Clamp(color.b - darknessAmount, 0, 255), - byte.MaxValue); + return new Color32((byte)Mathf.Clamp(color.r - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.g - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.b - darknessAmount, 0, 255), byte.MaxValue); } /// @@ -632,9 +573,7 @@ public static class Extensions /// A truncated string of maxLength with the attached suffix. public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…") { - return value?.Length > maxLength - ? value[..maxLength] + truncationSuffix - : value; + return value?.Length > maxLength ? value[..maxLength] + truncationSuffix : value; } /// @@ -670,10 +609,7 @@ public static class Extensions count += length; } - if (current.Count > 0) - { - chunks.Enqueue([.. current]); - } + if (current.Count > 0) chunks.Enqueue([.. current]); return chunks; } @@ -685,9 +621,7 @@ public static class Extensions /// if the is for a custom options, otherwise. public static bool IsCustom(this OptionBehaviour optionBehaviour) { - return ModdedOptionsManager.ModdedOptions.Values.Any( - opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour) - || optionBehaviour.Equals(GameModeOption.OptionBehaviour); + return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour) || optionBehaviour.Equals(GameModeOption.OptionBehaviour); } /// @@ -740,9 +674,7 @@ public static class Extensions /// The if it is found, or there is none within the radius. public static DeadBody? GetNearestDeadBody(this PlayerControl playerControl, float radius) { - return Helpers - .GetNearestDeadBodies(playerControl.GetTruePosition(), radius, Helpers.CreateFilter(Constants.NotShipMask)) - .Find(component => component && !component.Reported); + return Helpers.GetNearestDeadBodies(playerControl.GetTruePosition(), radius, Helpers.CreateFilter(Constants.NotShipMask)).Find(component => component && !component.Reported); } /// @@ -755,16 +687,9 @@ public static class Extensions /// Optional predicate to test if the is valid. /// The type of the object. /// The if it was found, or if there is none within the radius. - public static T? GetNearestObjectOfType( - this PlayerControl playerControl, - float radius, - ContactFilter2D filter, - string? colliderTag = null, - Predicate? predicate = null) - where T : Component + public static T? GetNearestObjectOfType(this PlayerControl playerControl, float radius, ContactFilter2D filter, string? colliderTag = null, Predicate? predicate = null) where T : Component { - return Helpers.GetNearestObjectsOfType(playerControl.GetTruePosition(), radius, filter, colliderTag) - .Find(predicate ?? (component => component)); + return Helpers.GetNearestObjectsOfType(playerControl.GetTruePosition(), radius, filter, colliderTag).Find(predicate ?? (component => component)); } /// @@ -777,21 +702,9 @@ public static class Extensions /// Determines if Ghosts are included. /// Optional predicate to test if the is valid. /// The closest if there is one, otherwise. - public static PlayerControl? GetClosestPlayer( - this PlayerControl playerControl, - bool includeImpostors, - float distance, - bool ignoreColliders = false, - bool includeGhosts = false, - Predicate? predicate = null) + public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance, bool ignoreColliders = false, bool includeGhosts = false, Predicate? predicate = null) { - var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders) - .Where( - playerInfo => !playerInfo.Data.Disconnected && - playerInfo.PlayerId != playerControl.PlayerId && - (includeGhosts || !playerInfo.Data.IsDead) && - (includeImpostors || !playerInfo.Data.Role.IsImpostor)) - .ToList(); + var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders).Where(playerInfo => !playerInfo.Data.Disconnected && playerInfo.PlayerId != playerControl.PlayerId && (includeGhosts || !playerInfo.Data.IsDead) && (includeImpostors || !playerInfo.Data.Role.IsImpostor)).ToList(); return predicate != null ? filteredPlayers.Find(predicate) : filteredPlayers.FirstOrDefault(); } @@ -828,9 +741,7 @@ public static class Extensions var action = userData.GetAction(userData.actions.Count - 1)!; action.name = id; - action.descriptiveName = group != null - ? $"{Palette.CrewmateRoleHeaderDarkBlue.ToTextColor()}{group.ReplaceLineEndings(" ")}\n{name}" - : name; + action.descriptiveName = group != null ? $"{Palette.CrewmateRoleHeaderDarkBlue.ToTextColor()}{group.ReplaceLineEndings(" ")}\n{name}" : name; action.categoryId = category; action.type = type; action.userAssignable = true; @@ -841,7 +752,7 @@ public static class Extensions _actionId = action.id, _elementType = ControllerElementType.Button, _axisContribution = Pole.Positive, - _keyboardKeyCode = key, + _keyboardKeyCode = key }; if (modifiers != null) @@ -852,7 +763,6 @@ public static class Extensions } userData.keyboardMaps[0].actionElementMaps.Add(map); - userData.joystickMaps[0].actionElementMaps.Add(map); return action; } -} +} \ No newline at end of file