Merge pull request #107 from All-Of-Us-Mods/controller-support

Controller Support
This commit is contained in:
Atony authored and GitHub committed 2026-10-17 23:10:47 -05:00
commit d90d3c0a28
19 files changed
+501 -430

No files matched your search

+3
View File
@@ -1,4 +1,5 @@
using MiraAPI.Hud; using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.Translation; using MiraAPI.Translation;
using MiraAPI.Utilities.Assets; using MiraAPI.Utilities.Assets;
using UnityEngine; using UnityEngine;
@@ -13,6 +14,8 @@ public class ThinkButton : CustomActionButton
public override int MaxUses => 1; public override int MaxUses => 1;
public override ButtonUsesMode UsesMode => ButtonUsesMode.PerRound; public override ButtonUsesMode UsesMode => ButtonUsesMode.PerRound;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton; public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override MiraKeybind Keybind => MiraGlobalKeybinds.SecondaryAbility;
protected override void OnClick() protected override void OnClick()
{ {
Button!.OverrideText("button.think.thinking".Translate()); Button!.OverrideText("button.think.thinking".Translate());
+1 -2
View File
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Globalization;
using System.Globalization;
using System.Linq; using System.Linq;
using MiraAPI.Events; using MiraAPI.Events;
using MiraAPI.Events.Mira; using MiraAPI.Events.Mira;
+2 -2
View File
@@ -1,4 +1,4 @@
using MiraAPI.Keybinds; using MiraAPI.Keybinds;
using Rewired; using Rewired;
namespace MiraAPI.Example; namespace MiraAPI.Example;
@@ -6,5 +6,5 @@ namespace MiraAPI.Example;
[RegisterCustomKeybinds] [RegisterCustomKeybinds]
public static class ExampleKeybinds 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]);
} }
+48 -119
View File
@@ -139,10 +139,7 @@ public abstract class CustomActionButton
/// <param name="parent">The parent of the button.</param> /// <param name="parent">The parent of the button.</param>
public virtual void CreateButton(Transform parent) public virtual void CreateButton(Transform parent)
{ {
if (Button) if (Button) return;
{
return;
}
UsesLeft = MaxUses; UsesLeft = MaxUses;
Timer = AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay ? 0 : InitialCooldown; Timer = AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay ? 0 : InitialCooldown;
@@ -152,19 +149,15 @@ public abstract class CustomActionButton
Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent); Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent);
Button.name = Name + "Button"; Button.name = Name + "Button";
Button.OverrideText(Name.Translate().ToUpperInvariant()); Button.OverrideText(Name.Translate().ToUpperInvariant());
Object.Destroy(Button.glyph.GetComponent<SpriteRenderer>());
Object.Destroy(Button.glyph.GetComponent<Image>());
Button.graphic.sprite = Sprite.LoadAsset(); Button.graphic.sprite = Sprite.LoadAsset();
Button.SetUsesRemaining(MaxUses); Button.SetUsesRemaining(MaxUses);
if (MaxUses <= 0) if (MaxUses <= 0) Button.SetInfiniteUses();
{
Button.SetInfiniteUses();
}
if (TextOutlineColor != Color.clear) if (TextOutlineColor != Color.clear) SetTextOutline(TextOutlineColor);
{
SetTextOutline(TextOutlineColor);
}
var pb = Button.GetComponent<PassiveButton>(); var pb = Button.GetComponent<PassiveButton>();
pb.OnClick = new Button.ButtonClickedEvent(); pb.OnClick = new Button.ButtonClickedEvent();
@@ -173,10 +166,7 @@ public abstract class CustomActionButton
// Invoke the generic button click event. // Invoke the generic button click event.
var genericEvent = new MiraButtonClickEvent(this); var genericEvent = new MiraButtonClickEvent(this);
MiraEventManager.InvokeEvent(genericEvent); MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled) if (genericEvent.IsCancelled) MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this));
{
MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this));
}
// Invoke the button click event for specific button. // Invoke the button click event for specific button.
var eventType = CustomButtonManager.ButtonEventTypes[GetType()]; var eventType = CustomButtonManager.ButtonEventTypes[GetType()];
@@ -191,68 +181,47 @@ public abstract class CustomActionButton
if (specificInvoked) if (specificInvoked)
{ {
if (!@event.IsCancelled) if (!@event.IsCancelled) ClickHandler();
{
ClickHandler();
}
} }
else else
{ {
if (!genericEvent.IsCancelled) if (!genericEvent.IsCancelled) ClickHandler();
{
ClickHandler();
}
} }
})); }));
if (Keybind == null) return; if (Keybind == null) return;
Keybind.OnActivate(() => Keybind.RemoveOnActivate(OnKeybind);
{ Keybind.OnActivate(OnKeybind);
if (!Enabled(PlayerControl.LocalPlayer.Data.Role)) return;
// Invoke the generic button click event. KeybindIcon = Helpers.CreateKeybindIcon(Button.gameObject, Keybind.CurrentKey, new Vector3(0.4f, 0.45f, -9f));
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));
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>(); KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
HudManagerPatches.ModdedKeybindIcons.Add(KeybindText); KeybindText.fontSizeMax = KeybindText.fontSize;
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f); 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<PassiveButton>().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<MiraApiSettings>.Instance.ShowKeybinds.Value);
} }
/// <summary> /// <summary>
@@ -262,22 +231,13 @@ public abstract class CustomActionButton
/// <param name="moveButton">Whether the button's position should change in-game.</param> /// <param name="moveButton">Whether the button's position should change in-game.</param>
public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true) public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true)
{ {
if (!HudManager.InstanceExists || Button == null) if (!HudManager.InstanceExists || Button == null) return;
{
return;
}
Location = location; Location = location;
if (!moveButton) if (!moveButton) return;
{
return;
}
if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null) if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null) return;
{
return;
}
switch (location) switch (location)
{ {
@@ -305,10 +265,7 @@ public abstract class CustomActionButton
public virtual void ResetCooldownAndOrEffect() public virtual void ResetCooldownAndOrEffect()
{ {
Timer = Cooldown; Timer = Cooldown;
if (EffectActive) if (EffectActive) OnEffectEnd();
{
OnEffectEnd();
}
EffectActive = false; EffectActive = false;
} }
@@ -328,10 +285,7 @@ public abstract class CustomActionButton
/// <param name="sprite">The new <see cref="UnityEngine.Sprite"/> to override with.</param> /// <param name="sprite">The new <see cref="UnityEngine.Sprite"/> to override with.</param>
public virtual void OverrideSprite(Sprite sprite) public virtual void OverrideSprite(Sprite sprite)
{ {
if (Button != null) if (Button != null) Button.graphic.sprite = sprite;
{
Button.graphic.sprite = sprite;
}
} }
/// <summary> /// <summary>
@@ -388,10 +342,7 @@ public abstract class CustomActionButton
UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue); UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue);
Button?.SetUsesRemaining(UsesLeft); Button?.SetUsesRemaining(UsesLeft);
if (Button != null) if (Button != null) Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white;
{
Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white;
}
} }
/// <summary> /// <summary>
@@ -469,8 +420,7 @@ public abstract class CustomActionButton
/// <returns>A value that represents whether the button should light up or not.</returns> /// <returns>A value that represents whether the button should light up or not.</returns>
public virtual bool CanUse() public virtual bool CanUse()
{ {
return PlayerControl.LocalPlayer.moveable && return PlayerControl.LocalPlayer.moveable && ((EffectActive && IsEffectCancellable()) || (!EffectActive && (!LimitedUses || UsesLeft > 0)));
((EffectActive && IsEffectCancellable()) || (!EffectActive && (!LimitedUses || UsesLeft > 0)));
} }
/// <summary> /// <summary>
@@ -498,10 +448,7 @@ public abstract class CustomActionButton
return; return;
} }
if (!CanClick()) if (!CanClick()) return;
{
return;
}
if (LimitedUses) if (LimitedUses)
{ {
@@ -530,20 +477,9 @@ public abstract class CustomActionButton
/// <param name="playerControl">The local PlayerControl.</param> /// <param name="playerControl">The local PlayerControl.</param>
public virtual void FixedUpdateHandler(PlayerControl 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<MiraApiSettings>.Instance.ShowKeybinds.Value &&
Keybind.CurrentKey != KeyboardKeyCode.None);
}
if (Timer >= 0) if (Timer >= 0)
{ {
if (!TimerPaused && (!PauseTimerInVent || !playerControl.inVent)) if (!TimerPaused && (!PauseTimerInVent || !playerControl.inVent)) Timer -= Time.deltaTime;
{
Timer -= Time.deltaTime;
}
} }
else if (HasEffect && EffectActive && EffectDuration > 0) else if (HasEffect && EffectActive && EffectDuration > 0)
{ {
@@ -555,13 +491,9 @@ public abstract class CustomActionButton
if (Button) if (Button)
{ {
if (CanUse()) if (CanUse())
{
Button!.SetEnabled(); Button!.SetEnabled();
}
else else
{
Button!.SetDisabled(); Button!.SetDisabled();
}
if (EffectActive && EffectDuration > 0) if (EffectActive && EffectDuration > 0)
{ {
@@ -623,10 +555,7 @@ public abstract class CustomActionButton<T> : CustomActionButton
public override bool CanUse() public override bool CanUse()
{ {
var newTarget = GetTarget(); var newTarget = GetTarget();
if (newTarget != Target) if (newTarget != Target) SetOutline(false);
{
SetOutline(false);
}
Target = IsTargetValid(newTarget) ? newTarget : null; Target = IsTargetValid(newTarget) ? newTarget : null;
SetOutline(true); SetOutline(true);
+6 -1
View File
@@ -1,4 +1,4 @@
using System; using System;
using Rewired; using Rewired;
namespace MiraAPI.Keybinds; namespace MiraAPI.Keybinds;
@@ -23,6 +23,11 @@ public class BaseKeybind
/// </summary> /// </summary>
public KeyboardKeyCode CurrentKey => KeybindUtils.GetKeycodeByKeybind(this); public KeyboardKeyCode CurrentKey => KeybindUtils.GetKeycodeByKeybind(this);
/// <summary>
/// Gets the display label of the currently assigned button on the active controller.
/// </summary>
public string CurrentControllerButton => KeybindUtils.GetControllerButtonByKeybind(this);
/// <summary> /// <summary>
/// Gets or sets the handler of the keybind. Invoked when the keybind is activated. /// Gets or sets the handler of the keybind. Invoked when the keybind is activated.
/// </summary> /// </summary>
+87
View File
@@ -0,0 +1,87 @@
namespace MiraAPI.Keybinds;
/// <summary>
/// Controller buttons used for default keybinds.
/// </summary>
public enum ControllerButton
{
/// <summary>
/// The bottom face button (Cross on PlayStation, A on Xbox).
/// </summary>
South = 4,
/// <summary>
/// The right face button (Circle on PlayStation, B on Xbox).
/// </summary>
East = 5,
/// <summary>
/// The left face button (Square on PlayStation, X on Xbox).
/// </summary>
West = 7,
/// <summary>
/// The top face button (Triangle on PlayStation, Y on Xbox).
/// </summary>
North = 8,
/// <summary>
/// The left bumper (L1 on PlayStation, LB on Xbox).
/// </summary>
LeftBumper = 10,
/// <summary>
/// The right bumper (R1 on PlayStation, RB on Xbox).
/// </summary>
RightBumper = 12,
/// <summary>
/// The left trigger (L2 on PlayStation, LT on Xbox).
/// </summary>
LeftTrigger = 11,
/// <summary>
/// The right trigger (R2 on PlayStation, RT on Xbox).
/// </summary>
RightTrigger = 13,
/// <summary>
/// The left stick button (L3 on PlayStation).
/// </summary>
LeftStick = 17,
/// <summary>
/// The right stick button (R3 on PlayStation).
/// </summary>
RightStick = 18,
/// <summary>
/// The back button (Create on PlayStation, View on Xbox).
/// </summary>
Back = 14,
/// <summary>
/// The start button (Options on PlayStation, Menu on Xbox).
/// </summary>
Start = 15,
/// <summary>
/// The D-pad up button.
/// </summary>
DPadUp = 19,
/// <summary>
/// The D-pad right button.
/// </summary>
DPadRight = 20,
/// <summary>
/// The D-pad down button.
/// </summary>
DPadDown = 21,
/// <summary>
/// The D-pad left button.
/// </summary>
DPadLeft = 22,
}
+84 -16
View File
@@ -33,12 +33,40 @@ public static class KeybindManager
continue; continue;
} }
keybind.RewiredInputAction = instance.userData.RegisterModBind( keybind.RewiredInputAction = instance.userData.RegisterModBind(keybind.Id, keybind.Name, keybind.SourcePluginName, keybind.DefaultKey, modifiers: keybind.ModifierKeys);
keybind.Id,
keybind.Name, if (keybind.DefaultControllerButton == null) continue;
keybind.SourcePluginName,
keybind.DefaultKey, var button = (int)keybind.DefaultControllerButton.Value;
modifiers: keybind.ModifierKeys); 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) catch (Exception e)
@@ -50,24 +78,66 @@ public static class KeybindManager
/// <summary> /// <summary>
/// Returns all conflicts where <see cref="MiraKeybind"/>s use the same key. /// Returns all conflicts where <see cref="MiraKeybind"/>s use the same key.
/// </summary> /// </summary>
/// <returns>A list of pairs of keybinds that conflict.</returns> /// <returns>The conflicting keybinds grouped by keycode.</returns>
public static Dictionary<KeyboardKeyCode, List<MiraKeybind>> GetConflicts() public static Dictionary<KeyboardKeyCode, List<MiraKeybind>> GetConflicts()
{ {
var conflicts = new Dictionary<KeyboardKeyCode, List<MiraKeybind>>(); var conflicts = new Dictionary<KeyboardKeyCode, List<MiraKeybind>>();
foreach (var keybind in Keybinds) 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 (all.Length > 1)
{ {
if (!conflicts.TryGetValue(keybind.CurrentKey, out var group)) if (!conflicts.TryGetValue(key, out var group))
{ {
conflicts.Add(keybind.CurrentKey, []); group = [];
group = conflicts[keybind.CurrentKey]; conflicts.Add(key, group);
} }
if (!group.Contains(keybind)) if (!group.Contains(keybind)) group.Add(keybind);
}
}
return conflicts;
}
/// <summary>
/// Returns controller bindings shared by exclusive custom keybinds.
/// </summary>
/// <param name="enabledOnly">Whether to check only active controller maps and available HUD buttons.</param>
/// <returns>The conflicting custom bindings.</returns>
public static List<ActionElementMap> GetControllerConflicts(bool enabledOnly = false)
{ {
group.Add(keybind); var conflicts = new List<ActionElementMap>();
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<ControllerMap>(player.controllers.maps.GetMaps(controller).Cast<CppCollections.IEnumerable<ControllerMap>>()).ToArray();
foreach (var map in maps)
{
if (enabledOnly && !map.enabled) continue;
foreach (var binding in map.GetElementMaps())
{
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,7 +149,7 @@ public static class KeybindManager
/// Returns all <see cref="MiraKeybind"/>s for a specified keycode. /// Returns all <see cref="MiraKeybind"/>s for a specified keycode.
/// </summary> /// </summary>
/// <param name="keyCode">The key to look for.</param> /// <param name="keyCode">The key to look for.</param>
/// /// <param name="exclusiveCheck">Should only exclusive keybinds be present.</param> /// <param name="exclusiveCheck">Whether to return only exclusive keybinds.</param>
/// <returns>The list of <see cref="MiraKeybind"/>s.</returns> /// <returns>The list of <see cref="MiraKeybind"/>s.</returns>
public static MiraKeybind[] GetKeybindsForKey(KeyboardKeyCode keyCode, bool exclusiveCheck = false) public static MiraKeybind[] GetKeybindsForKey(KeyboardKeyCode keyCode, bool exclusiveCheck = false)
{ {
@@ -87,10 +157,8 @@ public static class KeybindManager
foreach (var keybind in Keybinds) foreach (var keybind in Keybinds)
{ {
if (keybind.CurrentKey == keyCode && (keybind.Exclusive || !exclusiveCheck)) if (keybind.CurrentKey == keyCode && (keybind.Exclusive || !exclusiveCheck))
{
all.Add(keybind); all.Add(keybind);
} }
}
return [.. all]; return [.. all];
} }
+42 -4
View File
@@ -1,10 +1,12 @@
using System; using System;
using System.Linq;
using MiraAPI.Hud;
using Rewired; using Rewired;
namespace MiraAPI.Keybinds; namespace MiraAPI.Keybinds;
/// <summary> /// <summary>
/// Collection of keybind-related utility classes. /// Collection of keybind-related utilities.
/// </summary> /// </summary>
public static class KeybindUtils public static class KeybindUtils
{ {
@@ -23,6 +25,25 @@ public static class KeybindUtils
return keybind.RewiredInputAction == null ? KeyboardKeyCode.None : GetKeycodeByActionId(keybind.RewiredInputAction.id); return keybind.RewiredInputAction == null ? KeyboardKeyCode.None : GetKeycodeByActionId(keybind.RewiredInputAction.id);
} }
/// <summary>
/// Returns the display label for the currently assigned controller button of a <see cref="BaseKeybind"/>.
/// </summary>
/// <param name="keybind">The keybind to get the controller button from.</param>
/// <returns>The button label, or an empty string if no controller binding is available.</returns>
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,
};
}
/// <summary> /// <summary>
/// Gets the keycode for an action with <see cref="ReInput"/>. /// Gets the keycode for an action with <see cref="ReInput"/>.
/// </summary> /// </summary>
@@ -37,23 +58,40 @@ public static class KeybindUtils
/// Gets a <see cref="ActionElementMap"/> by id with <see cref="ReInput"/>. /// Gets a <see cref="ActionElementMap"/> by id with <see cref="ReInput"/>.
/// </summary> /// </summary>
/// <param name="actionId">The action ID.</param> /// <param name="actionId">The action ID.</param>
/// <returns>The keyboard keycode.</returns> /// <returns>The keyboard binding, or null if the action is unassigned.</returns>
public static ActionElementMap? GetActionElementMap(int actionId) public static ActionElementMap? GetActionElementMap(int actionId)
{ {
var player = ReInput.players.GetPlayer(0); var player = ReInput.players.GetPlayer(0);
return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false); return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false);
} }
/// <summary>
/// Gets the binding for an action on the active controller.
/// </summary>
/// <param name="actionId">The action ID.</param>
/// <returns>The controller binding, or null if no active controller or binding is available.</returns>
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);
}
/// <summary> /// <summary>
/// Gets an <see cref="InputAction"/> by id. /// Gets an <see cref="InputAction"/> by id.
/// </summary> /// </summary>
/// <param name="id">The action identifier name.</param> /// <param name="id">The action ID.</param>
/// <returns>The keyboard keycode.</returns> /// <returns>The input action, or null if it is unavailable.</returns>
public static InputAction? GetInputActionById(int id) public static InputAction? GetInputActionById(int id)
{ {
return RewiredInputManager?.userData?.GetActionById(id); return RewiredInputManager?.userData?.GetActionById(id);
} }
internal static bool IsControllerActionActive(int actionId)
{
return CustomButtonManager.Buttons.Any(x => x.Keybind?.RewiredInputAction?.id == actionId && x.Button != null && x.Button.isActiveAndEnabled && x.Enabled(PlayerControl.LocalPlayer?.Data?.Role));
}
/// <summary> /// <summary>
/// Finds and returns an unused KeyCode that is not equal to the excluded key. /// Finds and returns an unused KeyCode that is not equal to the excluded key.
/// </summary> /// </summary>
+2 -1
View File
@@ -1,10 +1,11 @@
using Rewired; using Rewired;
namespace MiraAPI.Keybinds; namespace MiraAPI.Keybinds;
/// <summary> /// <summary>
/// Class containing all default <see cref="MiraKeybind"/>s. /// Class containing all default <see cref="MiraKeybind"/>s.
/// Recommended for cross-mod compatibility. /// Recommended for cross-mod compatibility.
/// Controller bindings are unassigned by default.
/// </summary> /// </summary>
[RegisterCustomKeybinds] [RegisterCustomKeybinds]
public static class MiraGlobalKeybinds public static class MiraGlobalKeybinds
+22 -6
View File
@@ -1,4 +1,4 @@
using System.Globalization; using System.Globalization;
using Rewired; using Rewired;
namespace MiraAPI.Keybinds; namespace MiraAPI.Keybinds;
@@ -10,11 +10,7 @@ namespace MiraAPI.Keybinds;
/// <param name="defaultKeycode">The default keycode.</param> /// <param name="defaultKeycode">The default keycode.</param>
/// <param name="modifierKeys">Up to 3 optional <see cref="ModifierKey"/>s.</param> /// <param name="modifierKeys">Up to 3 optional <see cref="ModifierKey"/>s.</param>
/// <param name="exclusive">Is exclusive.</param> /// <param name="exclusive">Is exclusive.</param>
public class MiraKeybind( public class MiraKeybind(string name, KeyboardKeyCode? defaultKeycode, ModifierKey[]? modifierKeys = null, bool exclusive = true) : BaseKeybind(name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_'))
string name,
KeyboardKeyCode? defaultKeycode,
ModifierKey[]? modifierKeys = null,
bool exclusive = true) : BaseKeybind(name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_'))
{ {
/// <summary> /// <summary>
/// Gets name of the keybind. /// Gets name of the keybind.
@@ -26,6 +22,11 @@ public class MiraKeybind(
/// </summary> /// </summary>
public KeyboardKeyCode DefaultKey { get; } = defaultKeycode ?? KeyboardKeyCode.None; public KeyboardKeyCode DefaultKey { get; } = defaultKeycode ?? KeyboardKeyCode.None;
/// <summary>
/// Gets the default controller button. If <see langword="null"/>, the controller binding is left unassigned.
/// </summary>
public ControllerButton? DefaultControllerButton { get; }
/// <summary> /// <summary>
/// Gets the <see cref="ModifierKey"/>s assigned to this keybind. /// Gets the <see cref="ModifierKey"/>s assigned to this keybind.
/// Due to Rewired limitations, there can only be 3 <see cref="ModifierKey"/>s. /// Due to Rewired limitations, there can only be 3 <see cref="ModifierKey"/>s.
@@ -34,11 +35,26 @@ public class MiraKeybind(
/// <summary> /// <summary>
/// Gets a value indicating whether this keybind should be checked for conflicts with other exclusive keybinds using the same key. /// 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.
/// </summary> /// </summary>
public bool Exclusive { get; } = exclusive; public bool Exclusive { get; } = exclusive;
internal string? SourcePluginName { get; set; } internal string? SourcePluginName { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraKeybind"/> class with a controller binding.
/// </summary>
/// <param name="name">The name of the keybind.</param>
/// <param name="defaultKeycode">The default keycode.</param>
/// <param name="defaultControllerButton">The default controller button.</param>
/// <param name="modifierKeys">Up to 3 optional <see cref="ModifierKey"/>s for the keyboard binding.</param>
/// <param name="exclusive">Is exclusive.</param>
public MiraKeybind(string name, KeyboardKeyCode? defaultKeycode, ControllerButton defaultControllerButton, ModifierKey[]? modifierKeys = null, bool exclusive = true)
: this(name, defaultKeycode, modifierKeys, exclusive)
{
DefaultControllerButton = defaultControllerButton;
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {
@@ -1,4 +1,4 @@
using System; using System;
namespace MiraAPI.Keybinds; namespace MiraAPI.Keybinds;
+1 -1
View File
@@ -1,4 +1,4 @@
using System.Globalization; using System.Globalization;
namespace MiraAPI.Keybinds; namespace MiraAPI.Keybinds;
+1 -1
View File
@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace MiraAPI.Keybinds; namespace MiraAPI.Keybinds;
+28 -112
View File
@@ -1,6 +1,5 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using HarmonyLib; using HarmonyLib;
using MiraAPI.Hud; using MiraAPI.Hud;
using MiraAPI.Keybinds; using MiraAPI.Keybinds;
@@ -28,47 +27,29 @@ public static class HudManagerPatches
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = []; private static Dictionary<TextMeshPro, int> 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<TextMeshPro> ModdedKeybindIcons = [];
public static IEnumerator CoResizeUI() public static IEnumerator CoResizeUI()
{ {
while (!HudManager.Instance) while (!HudManager.Instance) yield return null;
{
yield return null;
}
yield return new WaitForSeconds(0.01f); yield return new WaitForSeconds(0.01f);
ResizeUI(LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value); ResizeUI(LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value);
} }
private static Transform _storedButtonsParent; private static Transform _storedButtonsParent;
public static void ResizeUI(float scaleFactor) public static void ResizeUI(float scaleFactor)
{ {
if (!_storedButtonsParent) if (!_storedButtonsParent) _storedButtonsParent = HudManager.Instance.transform.FindChild("Buttons");
{
_storedButtonsParent = HudManager.Instance.transform.FindChild("Buttons");
}
if (_storedButtonsParent) if (_storedButtonsParent)
{ {
foreach (var aspect in _storedButtonsParent.GetComponentsInChildren<AspectPosition>(true)) foreach (var aspect in _storedButtonsParent.GetComponentsInChildren<AspectPosition>(true))
{ {
if (!aspect.gameObject) if (!aspect.gameObject) continue;
{
continue;
}
if (aspect.gameObject.transform.parent.name == "TopRight") if (aspect.gameObject.transform.parent.name == "TopRight") continue;
{
continue;
}
if (aspect.gameObject.name.Contains("TopRight")) if (aspect.gameObject.name.Contains("TopRight")) continue;
{
continue;
}
aspect.gameObject.SetActive(!aspect.isActiveAndEnabled); aspect.gameObject.SetActive(!aspect.isActiveAndEnabled);
aspect.DistanceFromEdge *= new Vector2(scaleFactor, scaleFactor); aspect.DistanceFromEdge *= new Vector2(scaleFactor, scaleFactor);
@@ -78,10 +59,7 @@ public static class HudManagerPatches
foreach (var button in HudManager.Instance.GetComponentsInChildren<ActionButton>(true)) foreach (var button in HudManager.Instance.GetComponentsInChildren<ActionButton>(true))
{ {
if (!button.gameObject) if (!button.gameObject) continue;
{
continue;
}
button.gameObject.SetActive(!button.isActiveAndEnabled); button.gameObject.SetActive(!button.isActiveAndEnabled);
button.gameObject.transform.localScale *= scaleFactor; button.gameObject.transform.localScale *= scaleFactor;
@@ -91,15 +69,9 @@ public static class HudManagerPatches
if (!_storedButtonsParent) return; if (!_storedButtonsParent) return;
foreach (var arrange in _storedButtonsParent.GetComponentsInChildren<GridArrange>(true)) foreach (var arrange in _storedButtonsParent.GetComponentsInChildren<GridArrange>(true))
{ {
if (!arrange.gameObject || !arrange.transform) if (!arrange.gameObject || !arrange.transform) continue;
{
continue;
}
if (arrange.gameObject.name.Contains("TopRight")) if (arrange.gameObject.name.Contains("TopRight")) continue;
{
continue;
}
arrange.gameObject.SetActive(!arrange.isActiveAndEnabled); arrange.gameObject.SetActive(!arrange.isActiveAndEnabled);
arrange.CellSize = new Vector2(scaleFactor, scaleFactor); arrange.CellSize = new Vector2(scaleFactor, scaleFactor);
@@ -143,10 +115,7 @@ public static class HudManagerPatches
[HarmonyPatch(typeof(ChatController), nameof(ChatController.Close))] [HarmonyPatch(typeof(ChatController), nameof(ChatController.Close))]
public static void TogglePrefix(ChatController __instance) public static void TogglePrefix(ChatController __instance)
{ {
if (!MiraHudHelper.ClonedChatButton) if (!MiraHudHelper.ClonedChatButton) return;
{
return;
}
__instance.chatButton.transform.localPosition = MiraHudHelper.ClonedChatButton.transform.localPosition + new Vector3(-0.3f, 0); __instance.chatButton.transform.localPosition = MiraHudHelper.ClonedChatButton.transform.localPosition + new Vector3(-0.3f, 0);
} }
@@ -179,25 +148,13 @@ public static class HudManagerPatches
public static void StartPostfix(HudManager __instance) public static void StartPostfix(HudManager __instance)
{ {
__instance.gameObject.AddComponent<MiraHudHelper>(); __instance.gameObject.AddComponent<MiraHudHelper>();
if (Buttons == null) if (Buttons == null) Buttons = __instance.transform.Find("Buttons");
{
Buttons = __instance.transform.Find("Buttons");
}
if (BottomRight == null) if (BottomRight == null) BottomRight = Buttons.Find("BottomRight");
{
BottomRight = Buttons.Find("BottomRight");
}
if (BottomLeft == null) if (BottomLeft == null) BottomLeft = Object.Instantiate(BottomRight.gameObject, Buttons);
{
BottomLeft = Object.Instantiate(BottomRight.gameObject, Buttons);
}
foreach (var t in BottomLeft.GetComponentsInChildren<ActionButton>(true)) foreach (var t in BottomLeft.GetComponentsInChildren<ActionButton>(true)) t.gameObject.Destroy();
{
t.gameObject.Destroy();
}
var gridArrange = BottomLeft.GetComponent<GridArrange>(); var gridArrange = BottomLeft.GetComponent<GridArrange>();
var aspectPosition = BottomLeft.GetComponent<AspectPosition>(); var aspectPosition = BottomLeft.GetComponent<AspectPosition>();
@@ -209,10 +166,7 @@ public static class HudManagerPatches
if (Constants.GetPlatformType() is Platforms.Android or Platforms.IPhone) if (Constants.GetPlatformType() is Platforms.Android or Platforms.IPhone)
{ {
var fakeButton = Object.Instantiate(__instance.AbilityButton, BottomLeft.transform); var fakeButton = Object.Instantiate(__instance.AbilityButton, BottomLeft.transform);
foreach (var renderer in fakeButton.GetComponentsInChildren<Renderer>()) foreach (var renderer in fakeButton.GetComponentsInChildren<Renderer>()) renderer.enabled = false;
{
renderer.enabled = false;
}
fakeButton.buttonLabelText.Destroy(); fakeButton.buttonLabelText.Destroy();
fakeButton.cooldownTimerText.Destroy(); fakeButton.cooldownTimerText.Destroy();
@@ -222,8 +176,6 @@ public static class HudManagerPatches
fakeButton.Destroy(); fakeButton.Destroy();
} }
ModdedKeybindIcons = [];
foreach (var button in CustomButtonManager.CustomButtons) foreach (var button in CustomButtonManager.CustomButtons)
{ {
var location = button.Location switch var location = button.Location switch
@@ -233,10 +185,7 @@ public static class HudManagerPatches
_ => null, _ => null,
}; };
if (location is null) if (location is null) continue;
{
continue;
}
try try
{ {
@@ -272,25 +221,21 @@ public static class HudManagerPatches
var buttonObj = kvp.Key; var buttonObj = kvp.Key;
var actionId = kvp.Value; 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<TextMeshPro>(), actionId);
if (!MiraApiPlugin.IsMobile) if (!MiraApiPlugin.IsMobile)
{ {
var comp = buttonObj.GetComponent<ActionButton>(); var comp = buttonObj.GetComponent<ActionButton>();
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp; 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<TextMeshPro>(), actionId);
} }
MiraApiSettings.OldButtonScaleFactor = MiraApiSettings.OldButtonScaleFactor = LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value;
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value; MiraApiSettings.OldUiButtonScaleFactor = LocalSettingsTabSingleton<MiraApiSettings>.Instance.TopRightButtonsFactorSlider.Value;
MiraApiSettings.OldUiButtonScaleFactor =
LocalSettingsTabSingleton<MiraApiSettings>.Instance.TopRightButtonsFactorSlider.Value;
Coroutines.Start(CoResizeUI()); Coroutines.Start(CoResizeUI());
} }
@@ -306,10 +251,7 @@ public static class HudManagerPatches
public static void SetHudActivePostfix(HudManager __instance, PlayerControl localPlayer, RoleBehaviour role, bool isActive) 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); __instance.AdminButton.ToggleVisible(isActive && role.IsImpostor && GameOptionsManager.Instance.CurrentGameOptions.GameMode == AmongUs.GameOptions.GameModes.HideNSeek);
if (localPlayer.Data == null) if (localPlayer.Data == null) return;
{
return;
}
foreach (var button in CustomButtonManager.CustomButtons) foreach (var button in CustomButtonManager.CustomButtons)
{ {
@@ -328,8 +270,7 @@ public static class HudManagerPatches
[HarmonyPostfix] [HarmonyPostfix]
public static void UpdatePostfix() public static void UpdatePostfix()
{ {
var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard && var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard && LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value;
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value;
foreach (var btnIcon in vanillaKeybindIcons) foreach (var btnIcon in vanillaKeybindIcons)
{ {
@@ -337,44 +278,19 @@ public static class HudManagerPatches
btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds); btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds);
} }
foreach (var btnIcon in ModdedKeybindIcons) foreach (var button in CustomButtonManager.CustomButtons) button.UpdateKeybindIcon();
{
btnIcon.transform.parent.gameObject.SetActive(canSeeBinds);
}
var player = ReInput.players.GetPlayer(0); var player = ReInput.players.GetPlayer(0);
var keyboard = player.controllers.Keyboard;
foreach (var entry in KeybindManager.Keybinds) foreach (var entry in KeybindManager.Keybinds)
{ {
var modKeys = entry.ModifierKeys; if (entry.RewiredInputAction != null && player.GetButtonDown(entry.RewiredInputAction.id))
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)
{
entry.Invoke(); entry.Invoke();
} }
}
foreach (var entry in KeybindManager.VanillaKeybinds.Values) foreach (var entry in KeybindManager.VanillaKeybinds.Values)
{ {
if (player.GetButtonDown(entry.Id)) if (player.GetButtonDown(entry.Id))
{
entry.Invoke(); entry.Invoke();
} }
} }
}
} }
@@ -1,6 +1,7 @@
using System.Linq; using System.Linq;
using HarmonyLib; using HarmonyLib;
using MiraAPI.Keybinds; using MiraAPI.Keybinds;
using Rewired;
using Rewired.UI.ControlMapper; using Rewired.UI.ControlMapper;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
@@ -10,53 +11,71 @@ namespace MiraAPI.Patches.Keybinds;
[HarmonyPatch(typeof(ControlMapper))] [HarmonyPatch(typeof(ControlMapper))]
public static class ControlMapperPatches public static class ControlMapperPatches
{ {
// Patching this crashes for Epic Games users [HarmonyPrefix]
/*[HarmonyPrefix] [HarmonyPatch(nameof(ControlMapper.Initialize))]
[HarmonyPatch(nameof(ControlMapper.Start))] private static void InitializePrefix(ControlMapper __instance)
private static void StartPrefix(ControlMapper __instance)
{ {
var doneButton = GameObject.Find("DoneButton")?.GetComponent<CustomButton>(); __instance.showControllers = true;
if (doneButton == null) __instance.showGlyphs = false;
{
return;
} }
var toggle = Object.Instantiate(doneButton, doneButton.transform.parent); [HarmonyPostfix]
var text = toggle.GetComponentInChildren<TextMeshProUGUI>(); [HarmonyPatch(nameof(ControlMapper.CreateLayout))]
var entry = LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds; private static void CreateLayoutPostfix(ControlMapper __instance)
toggle.gameObject.name = "KeybindsVisibleToggle";
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}";
toggle.onClick.RemoveAllListeners();
toggle.GetComponent<ButtonInfo>().identifier = "KeybindsVisibleToggle";
toggle.onClick.AddListener((UnityAction)(() =>
{ {
entry.Value = !entry.Value; __instance.references.controllerGroup.gameObject.SetActive(false);
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}"; __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] [HarmonyPrefix]
[HarmonyPatch(nameof(ControlMapper.Update))] [HarmonyPatch(nameof(ControlMapper.Update))]
private static void UpdatePrefix(ControlMapper __instance) private static void UpdatePrefix(ControlMapper __instance)
{ {
var conflicts = KeybindManager.GetConflicts();
var controllerConflicts = KeybindManager.GetControllerConflicts();
foreach (var element in __instance.themedElements) foreach (var element in __instance.themedElements)
{ {
var info = element.GetComponent<InputFieldInfo>(); var info = element.GetComponent<InputFieldInfo>();
if (info == null) if (info == null) continue;
{
continue;
}
var key = info.glyphOrText?.actionElementMap?.keyboardKeyCode; var map = ReInput.mapping.GetActionElementMap(info.actionElementMapId);
if (key == null) var image = element.GetComponent<Image>();
{ if (image == null) continue;
continue;
}
var conflicts = KeybindManager.GetConflicts(); var hasConflict = map?.controllerMap.controllerType switch
if (conflicts.Any(x => x.Key.ToString() == key.ToString()))
{ {
element.GetComponent<Image>().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();
} }
} }
} }
+6 -5
View File
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using HarmonyLib; using HarmonyLib;
using MiraAPI.Keybinds; using MiraAPI.Keybinds;
using Rewired; using Rewired;
@@ -10,19 +12,18 @@ public static class KeybindMenuPatch
private static bool _registered; private static bool _registered;
[HarmonyPrefix] [HarmonyPrefix]
// ReSharper disable once InconsistentNaming
private static void AwakePrefix(InputManager_Base __instance) private static void AwakePrefix(InputManager_Base __instance)
{ {
if (_registered) if (_registered) return;
{
return;
}
KeybindUtils.RewiredInputManager = __instance; KeybindUtils.RewiredInputManager = __instance;
KeybindManager.RewiredInit(); KeybindManager.RewiredInit();
_registered = true; _registered = true;
// Set these here right after input manager initializes // Set these here right after input manager initializes
KeybindManager.VanillaKeybinds = new() KeybindManager.VanillaKeybinds = new Dictionary<Type, VanillaKeybind>
{ {
{ typeof(KillButton), new VanillaKeybind(8) }, { typeof(KillButton), new VanillaKeybind(8) },
{ typeof(UseButton), new VanillaKeybind(6) }, { typeof(UseButton), new VanillaKeybind(6) },
@@ -1,4 +1,5 @@
using HarmonyLib; using System.Linq;
using HarmonyLib;
using MiraAPI.Keybinds; using MiraAPI.Keybinds;
using UnityEngine; using UnityEngine;
@@ -10,40 +11,42 @@ public static class OptionsMenuBehaviourPatch
private static ButtonRolloverHandler? _remapRollover; private static ButtonRolloverHandler? _remapRollover;
private static SpriteRenderer? _remapBackground; 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] [HarmonyPostfix]
[HarmonyPatch(nameof(OptionsMenuBehaviour.Open))] [HarmonyPatch(nameof(OptionsMenuBehaviour.Open))]
private static void OpenPostfix() private static void OpenPostfix(OptionsMenuBehaviour __instance)
{ {
_remapRollover = GameObject.Find("Remap Controls")?.GetComponent<ButtonRolloverHandler>()!; _remapRollover = __instance.GetComponentsInChildren<ButtonRolloverHandler>(true).FirstOrDefault(x => x.name == "Remap Controls");
try _remapBackground = _remapRollover?.transform.FindChild("Background")?.GetComponent<SpriteRenderer>();
{ if (_remapBackground != null) _remapBackground.color = Conflicts ? Color.red : Color.white;
_remapBackground = _remapRollover.transform.FindChild("Background").GetComponent<SpriteRenderer>();
_remapBackground.color = Conflicts ? Color.red : Color.white;
} }
catch
[HarmonyPrefix]
[HarmonyPatch(nameof(OptionsMenuBehaviour.Update))]
private static bool UpdatePrefix(OptionsMenuBehaviour __instance)
{ {
// ignored, this normally breaks when on mobile since there's no remap button screen. if (MiraApiPlugin.IsMobile || ActiveInputManager.currentControlType != ActiveInputManager.InputType.Joystick) return true;
}
if (Input.GetKeyUp(KeyCode.Escape) && !__instance.KeyboardOptions.activeSelf) __instance.Close();
__instance.KeyboardOptions.SetActive(true);
__instance.MouseAndKeyboardOptions.SetActive(true);
return false;
} }
[HarmonyPostfix] [HarmonyPostfix]
[HarmonyPatch(nameof(OptionsMenuBehaviour.Update))] [HarmonyPatch(nameof(OptionsMenuBehaviour.Update))]
private static void UpdatePostfix() private static void UpdatePostfix()
{ {
if (_remapRollover == null) if (_remapRollover == null) return;
{
return;
}
if (_remapBackground == null) if (_remapBackground == null) return;
{
return;
}
_remapRollover.OutColor = Conflicts ? Color.red : Color.white; var conflicts = Conflicts;
_remapRollover.UnselectedColor = Conflicts ? Color.red : Color.white; _remapRollover.OutColor = conflicts ? Color.red : Color.white;
_remapRollover.OverColor = Conflicts ? new Color32(255, 55, 55, 255) : Palette.AcceptedGreen; _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;
} }
} }
@@ -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<InputActionSourceData>(__instance.GetCurrentInputSources(__0).Cast<CppCollections.IEnumerable<InputActionSourceData>>()).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<InputActionSourceData>(__instance.GetCurrentInputSources(custom.RewiredInputAction.id).Cast<CppCollections.IEnumerable<InputActionSourceData>>()).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);
}
}
+26 -104
View File
@@ -43,9 +43,7 @@ public static class Extensions
chance = 0; chance = 0;
} }
return new NetData( return new NetData(RoleId.Get(role), BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
RoleId.Get(role),
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
} }
/// <summary> /// <summary>
@@ -60,20 +58,11 @@ public static class Extensions
return; return;
} }
if (self.CloseSound && Constants.ShouldPlaySfx()) if (self.CloseSound && Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(self.CloseSound, false);
{
SoundManager.Instance.PlaySound(self.CloseSound, false);
}
if (PlayerControl.LocalPlayer.Data.Role.TeamType == RoleTeamTypes.Crewmate) if (PlayerControl.LocalPlayer.Data.Role.TeamType == RoleTeamTypes.Crewmate) GameManager.Instance.LogicMinigame.OnMinigameClose();
{
GameManager.Instance.LogicMinigame.OnMinigameClose();
}
if (PlayerControl.LocalPlayer) if (PlayerControl.LocalPlayer) PlayerControl.HideCursorTemporarily();
{
PlayerControl.HideCursorTemporarily();
}
self.amClosing = Minigame.CloseState.Closing; self.amClosing = Minigame.CloseState.Closing;
self.logger.Info(string.Concat("Closing minigame ", self.GetType().Name)); self.logger.Info(string.Concat("Closing minigame ", self.GetType().Name));
@@ -145,9 +134,7 @@ public static class Extensions
{ {
var list = input.ToList(); var list = input.ToList();
return list.Count == 0 return list.Count == 0 ? throw new InvalidOperationException("Cannot get random element from an empty collection.") : list[UnityEngine.Random.Range(0, list.Count)];
? throw new InvalidOperationException("Cannot get random element from an empty collection.")
: list[UnityEngine.Random.Range(0, list.Count)];
} }
/// <summary> /// <summary>
@@ -201,10 +188,7 @@ public static class Extensions
// ReSharper disable once InconsistentNaming (Justification: Acronym.) // ReSharper disable once InconsistentNaming (Justification: Acronym.)
var il2cppList = new CppCollections.List<T>(); var il2cppList = new CppCollections.List<T>();
foreach (var item in systemList) foreach (var item in systemList) il2cppList.Add(item);
{
il2cppList.Add(item);
}
return il2cppList; return il2cppList;
} }
@@ -273,7 +257,6 @@ public static class Extensions
// Left as is intentionally, did you guys mean to use it in DeepDestroy? // 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", "IDE0051:Remove unused private members", Justification = "Not known until intent it clear.")]
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Unclear, but please shut up.")] [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.) #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 UnusedParameter.Local (Justification: Read above.)
// ReSharper disable once UnusedMember.Local (Justification: Read above.) // ReSharper disable once UnusedMember.Local (Justification: Read above.)
@@ -383,10 +366,7 @@ public static class Extensions
/// <returns>An <see cref="IEnumerable"/> that contains <see cref="GameObject"/>s.</returns> /// <returns>An <see cref="IEnumerable"/> that contains <see cref="GameObject"/>s.</returns>
public static IEnumerable<GameObject> GetAllChildren(this GameObject go) public static IEnumerable<GameObject> GetAllChildren(this GameObject go)
{ {
for (var i = 0; i < go.transform.childCount; i++) for (var i = 0; i < go.transform.childCount; i++) yield return go.transform.GetChild(i).gameObject;
{
yield return go.transform.GetChild(i).gameObject;
}
} }
/// <summary> /// <summary>
@@ -396,10 +376,7 @@ public static class Extensions
/// <returns>An <see cref="IEnumerable"/> that contains <see cref="GameObject"/>s.</returns> /// <returns>An <see cref="IEnumerable"/> that contains <see cref="GameObject"/>s.</returns>
public static IEnumerable<GameObject> GetAllChildren(this Transform go) public static IEnumerable<GameObject> GetAllChildren(this Transform go)
{ {
for (var i = 0; i < go.transform.childCount; i++) for (var i = 0; i < go.transform.childCount; i++) yield return go.transform.GetChild(i).gameObject;
{
yield return go.transform.GetChild(i).gameObject;
}
} }
/// <summary> /// <summary>
@@ -410,17 +387,12 @@ public static class Extensions
public static void SetSizeLimit(this SpriteRenderer sprite, float pixelSize) public static void SetSizeLimit(this SpriteRenderer sprite, float pixelSize)
{ {
sprite.drawMode = SpriteDrawMode.Sliced; sprite.drawMode = SpriteDrawMode.Sliced;
if (!sprite.sprite) if (!sprite.sprite) return;
{
return;
}
var spriteWidth = sprite.sprite.rect.width; var spriteWidth = sprite.sprite.rect.width;
var spriteHeight = sprite.sprite.rect.height; var spriteHeight = sprite.sprite.rect.height;
sprite.size = spriteWidth < spriteHeight sprite.size = spriteWidth < spriteHeight ? new Vector2(pixelSize * spriteWidth / spriteHeight, pixelSize) : new Vector2(pixelSize, pixelSize * spriteHeight / spriteWidth);
? new Vector2(pixelSize * spriteWidth / spriteHeight, pixelSize)
: new Vector2(pixelSize, pixelSize * spriteHeight / spriteWidth);
} }
/// <summary> /// <summary>
@@ -430,10 +402,7 @@ public static class Extensions
/// <param name="pixelSize">The scale for the sprite to be adjusted to.</param> /// <param name="pixelSize">The scale for the sprite to be adjusted to.</param>
public static void SetSizeLimit(this GameObject spriteObj, float pixelSize) public static void SetSizeLimit(this GameObject spriteObj, float pixelSize)
{ {
if (!spriteObj.TryGetComponent<SpriteRenderer>(out var sprite)) if (!spriteObj.TryGetComponent<SpriteRenderer>(out var sprite)) return;
{
return;
}
sprite.SetSizeLimit(pixelSize); sprite.SetSizeLimit(pixelSize);
} }
@@ -450,16 +419,10 @@ public static class Extensions
/// <returns>A <see cref="PlayerVoteData"/> if there is one, <see langword="null"/> otherwise.</returns> /// <returns>A <see cref="PlayerVoteData"/> if there is one, <see langword="null"/> otherwise.</returns>
public static PlayerVoteData GetVoteData(this PlayerControl player) public static PlayerVoteData GetVoteData(this PlayerControl player)
{ {
if (VoteDataComponents.TryGetValue(player, out var component)) if (VoteDataComponents.TryGetValue(player, out var component)) return component;
{
return component;
}
component = player.GetComponent<PlayerVoteData>(); component = player.GetComponent<PlayerVoteData>();
if (!component) if (!component) throw new InvalidOperationException("PlayerVoteData is not attached to the player.");
{
throw new InvalidOperationException("PlayerVoteData is not attached to the player.");
}
VoteDataComponents[player] = component; VoteDataComponents[player] = component;
return component; return component;
@@ -525,12 +488,7 @@ public static class Extensions
/// <returns>The best constructor.</returns> /// <returns>The best constructor.</returns>
public static ConstructorInfo? GetBestConstructor(this Type type, params object[] args) public static ConstructorInfo? GetBestConstructor(this Type type, params object[] args)
{ {
return type.GetValidConstructors(args) return type.GetValidConstructors(args).OrderBy(ctor => ctor.GetParameters().Select((p, i) => GetInheritanceDistance(args[i].GetType(), p.ParameterType)).Sum()).FirstOrDefault();
.OrderBy(
ctor => ctor.GetParameters()
.Select((p, i) => GetInheritanceDistance(args[i].GetType(), p.ParameterType))
.Sum())
.FirstOrDefault();
} }
/// <summary> /// <summary>
@@ -552,13 +510,10 @@ public static class Extensions
/// <returns>A collection of valid constructors.</returns> /// <returns>A collection of valid constructors.</returns>
public static IEnumerable<ConstructorInfo> GetValidConstructors(this Type type, params object[] args) public static IEnumerable<ConstructorInfo> GetValidConstructors(this Type type, params object[] args)
{ {
return type.GetConstructors().Where( return type.GetConstructors().Where(x =>
x =>
{ {
var parameters = x.GetParameters(); var parameters = x.GetParameters();
return parameters.Length == args.Length && Array.TrueForAll( return parameters.Length == args.Length && Array.TrueForAll(parameters, t => t.ParameterType.IsInstanceOfType(args[t.Position]));
parameters,
t => t.ParameterType.IsInstanceOfType(args[t.Position]));
}); });
} }
@@ -571,10 +526,7 @@ public static class Extensions
/// <returns>The distance between the types.</returns> /// <returns>The distance between the types.</returns>
public static int GetInheritanceDistance(Type from, Type to) public static int GetInheritanceDistance(Type from, Type to)
{ {
if (!from.IsAssignableFrom(to)) if (!from.IsAssignableFrom(to)) return int.MaxValue;
{
return int.MaxValue;
}
var type = from; var type = from;
var distance = 0; var distance = 0;
@@ -648,9 +600,7 @@ public static class Extensions
/// <returns>A truncated string of maxLength with the attached suffix.</returns> /// <returns>A truncated string of maxLength with the attached suffix.</returns>
public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…") public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…")
{ {
return value?.Length > maxLength return value?.Length > maxLength ? value[..maxLength] + truncationSuffix : value;
? value[..maxLength] + truncationSuffix
: value;
} }
/// <summary> /// <summary>
@@ -686,10 +636,7 @@ public static class Extensions
count += length; count += length;
} }
if (current.Count > 0) if (current.Count > 0) chunks.Enqueue([.. current]);
{
chunks.Enqueue([.. current]);
}
return chunks; return chunks;
} }
@@ -701,9 +648,7 @@ public static class Extensions
/// <returns><see langword="true"/> if the <see cref="OptionBehaviour"/> is for a custom options, <see langword="false"/> otherwise.</returns> /// <returns><see langword="true"/> if the <see cref="OptionBehaviour"/> is for a custom options, <see langword="false"/> otherwise.</returns>
public static bool IsCustom(this OptionBehaviour optionBehaviour) public static bool IsCustom(this OptionBehaviour optionBehaviour)
{ {
return ModdedOptionsManager.ModdedOptions.Values.Any( return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour) || optionBehaviour.Equals(GameModeOption.OptionBehaviour);
opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour)
|| optionBehaviour.Equals(GameModeOption.OptionBehaviour);
} }
/// <summary> /// <summary>
@@ -756,9 +701,7 @@ public static class Extensions
/// <returns>The <see cref="DeadBody"/> if it is found, or <see langword="null"/> there is none within the radius.</returns> /// <returns>The <see cref="DeadBody"/> if it is found, or <see langword="null"/> there is none within the radius.</returns>
public static DeadBody? GetNearestDeadBody(this PlayerControl playerControl, float radius) public static DeadBody? GetNearestDeadBody(this PlayerControl playerControl, float radius)
{ {
return Helpers return Helpers.GetNearestDeadBodies(playerControl.GetTruePosition(), radius, Helpers.CreateFilter(Constants.NotShipMask)).Find(component => component && !component.Reported);
.GetNearestDeadBodies(playerControl.GetTruePosition(), radius, Helpers.CreateFilter(Constants.NotShipMask))
.Find(component => component && !component.Reported);
} }
/// <summary> /// <summary>
@@ -771,16 +714,10 @@ public static class Extensions
/// <param name="predicate">Optional predicate to test if the <typeparamref name="T"/> is valid.</param> /// <param name="predicate">Optional predicate to test if the <typeparamref name="T"/> is valid.</param>
/// <typeparam name="T">The type of the object.</typeparam> /// <typeparam name="T">The type of the object.</typeparam>
/// <returns>The <typeparamref name="T"/> if it was found, or <see langword="null"/> if there is none within the radius.</returns> /// <returns>The <typeparamref name="T"/> if it was found, or <see langword="null"/> if there is none within the radius.</returns>
public static T? GetNearestObjectOfType<T>( public static T? GetNearestObjectOfType<T>(this PlayerControl playerControl, float radius, ContactFilter2D filter, string? colliderTag = null, Predicate<T>? predicate = null)
this PlayerControl playerControl,
float radius,
ContactFilter2D filter,
string? colliderTag = null,
Predicate<T>? predicate = null)
where T : Component where T : Component
{ {
return Helpers.GetNearestObjectsOfType<T>(playerControl.GetTruePosition(), radius, filter, colliderTag) return Helpers.GetNearestObjectsOfType<T>(playerControl.GetTruePosition(), radius, filter, colliderTag).Find(predicate ?? (component => component));
.Find(predicate ?? (component => component));
} }
/// <summary> /// <summary>
@@ -793,21 +730,9 @@ public static class Extensions
/// <param name="includeGhosts">Determines if Ghosts are included.</param> /// <param name="includeGhosts">Determines if Ghosts are included.</param>
/// <param name="predicate">Optional predicate to test if the <see cref="PlayerControl"/> is valid.</param> /// <param name="predicate">Optional predicate to test if the <see cref="PlayerControl"/> is valid.</param>
/// <returns>The closest <see cref="PlayerControl"/> if there is one, <see langword="false"/> otherwise.</returns> /// <returns>The closest <see cref="PlayerControl"/> if there is one, <see langword="false"/> otherwise.</returns>
public static PlayerControl? GetClosestPlayer( public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance, bool ignoreColliders = false, bool includeGhosts = false, Predicate<PlayerControl>? predicate = null)
this PlayerControl playerControl,
bool includeImpostors,
float distance,
bool ignoreColliders = false,
bool includeGhosts = false,
Predicate<PlayerControl>? predicate = null)
{ {
var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders) 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();
.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(); return predicate != null ? filteredPlayers.Find(predicate) : filteredPlayers.FirstOrDefault();
} }
@@ -844,9 +769,7 @@ public static class Extensions
var action = userData.GetAction(userData.actions.Count - 1)!; var action = userData.GetAction(userData.actions.Count - 1)!;
action.name = id; action.name = id;
action.descriptiveName = group != null action.descriptiveName = group != null ? $"<b><size=70%>{Palette.CrewmateRoleHeaderDarkBlue.ToTextColor()}{group.ReplaceLineEndings(" ")}</color></size></b>\n{name}" : name;
? $"<b><size=70%>{Palette.CrewmateRoleHeaderDarkBlue.ToTextColor()}{group.ReplaceLineEndings(" ")}</color></size></b>\n{name}"
: name;
action.categoryId = category; action.categoryId = category;
action.type = type; action.type = type;
action.userAssignable = true; action.userAssignable = true;
@@ -868,7 +791,6 @@ public static class Extensions
} }
userData.keyboardMaps[0].actionElementMaps.Add(map); userData.keyboardMaps[0].actionElementMaps.Add(map);
userData.joystickMaps[0].actionElementMaps.Add(map);
return action; return action;
} }