mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Implement Keybind API
This commit is contained in:
1 parent
0ff60071c2
commit
d9d0d6bc62
11 files changed
+255
-10
No files matched your search
@@ -6,6 +6,7 @@ using MiraAPI.Hud;
|
|||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Rewired;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Example.Buttons.Freezer;
|
namespace MiraAPI.Example.Buttons.Freezer;
|
||||||
@@ -19,6 +20,7 @@ public class FreezeButton : CustomActionButton<PlayerControl>
|
|||||||
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
|
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
|
||||||
|
|
||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||||
|
public override KeyboardKeyCode CurrentKeybind => KeyboardKeyCode.T;
|
||||||
|
|
||||||
protected override void OnClick()
|
protected override void OnClick()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Rewired;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Example.Buttons;
|
namespace MiraAPI.Example.Buttons;
|
||||||
@@ -15,6 +16,7 @@ public class MeetingButton : CustomActionButton
|
|||||||
public override int MaxUses => 3;
|
public override int MaxUses => 3;
|
||||||
|
|
||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||||
|
public override KeyboardKeyCode CurrentKeybind => KeyboardKeyCode.P;
|
||||||
|
|
||||||
public override bool Enabled(RoleBehaviour? role)
|
public override bool Enabled(RoleBehaviour? role)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using MiraAPI.Example.Roles;
|
|||||||
using MiraAPI.GameEnd;
|
using MiraAPI.GameEnd;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Rewired;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Example.Buttons;
|
namespace MiraAPI.Example.Buttons;
|
||||||
@@ -12,6 +13,7 @@ public class NeutralKillerButton : CustomActionButton
|
|||||||
public override string Name => "Win Game";
|
public override string Name => "Win Game";
|
||||||
public override float Cooldown => 0f;
|
public override float Cooldown => 0f;
|
||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||||
|
public override KeyboardKeyCode CurrentKeybind => KeyboardKeyCode.K;
|
||||||
protected override void OnClick()
|
protected override void OnClick()
|
||||||
{
|
{
|
||||||
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using MiraAPI.GameOptions;
|
|||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
|
using Rewired;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Example.Buttons.Teleporter;
|
namespace MiraAPI.Example.Buttons.Teleporter;
|
||||||
@@ -21,7 +22,7 @@ public class TeleportButton : CustomActionButton
|
|||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
||||||
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
|
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
|
||||||
public static bool IsZoom { get; private set; }
|
public static bool IsZoom { get; private set; }
|
||||||
|
public override KeyboardKeyCode CurrentKeybind => KeyboardKeyCode.M;
|
||||||
public override bool Enabled(RoleBehaviour? role)
|
public override bool Enabled(RoleBehaviour? role)
|
||||||
{
|
{
|
||||||
return role is TeleporterRole;
|
return role is TeleporterRole;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using MiraAPI.Events.Mira;
|
|||||||
using MiraAPI.Patches;
|
using MiraAPI.Patches;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Rewired;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@@ -52,6 +53,11 @@ public abstract class CustomActionButton
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual int MaxUses => 0;
|
public virtual int MaxUses => 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current key binding for this button.
|
||||||
|
/// </summary>
|
||||||
|
public abstract KeyboardKeyCode CurrentKeybind { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the button's text outline color.
|
/// Gets the button's text outline color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Rewired;
|
||||||
|
|
||||||
|
namespace MiraAPI.Keybinds
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lets you register and manage mod keybinds with conflict checks and rebinding support.
|
||||||
|
/// </summary>
|
||||||
|
public static class KeybindManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stores all info for a registered keybind.
|
||||||
|
/// </summary>
|
||||||
|
public class KeybindEntry
|
||||||
|
{
|
||||||
|
/// <summary>Unique identifier for this keybind.</summary>
|
||||||
|
public string Id;
|
||||||
|
|
||||||
|
/// <summary>Text shown in the Among Us rebinding menu to describe this keybind.</summary>
|
||||||
|
public string Description;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The method that runs when this keybind is pressed.
|
||||||
|
/// </summary>
|
||||||
|
public Action Handler;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The key currently assigned to this keybind.
|
||||||
|
/// </summary>
|
||||||
|
public KeyboardKeyCode Key;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If true, this keybind will be checked for conflicts with other exclusive keybinds using the same key.
|
||||||
|
/// </summary>
|
||||||
|
public bool Exclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly List<KeybindEntry> Registered = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new mod keybind.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Unique identifier for the keybind.</param>
|
||||||
|
/// <param name="description">Description to display in UI.</param>
|
||||||
|
/// <param name="key">Default key to use if no rebind exists.</param>
|
||||||
|
/// <param name="handler">Method to invoke when the keybind is pressed.</param>
|
||||||
|
/// <param name="exclusive">If true, conflicts with other exclusive keybinds on the same key.</param>
|
||||||
|
/// <returns> Returns the key that was registered for this keybind. </returns>
|
||||||
|
public static KeyboardKeyCode Register(string id, string description, KeyboardKeyCode key, Action handler, bool exclusive = true)
|
||||||
|
{
|
||||||
|
Registered.Add(new KeybindEntry
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Description = description,
|
||||||
|
Key = key,
|
||||||
|
Handler = handler,
|
||||||
|
Exclusive = exclusive,
|
||||||
|
});
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all conflicts where exclusive keybinds use the same key.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of pairs of keybinds that conflict.</returns>
|
||||||
|
public static List<(KeybindEntry, KeybindEntry)> GetConflicts()
|
||||||
|
{
|
||||||
|
var conflicts = new List<(KeybindEntry, KeybindEntry)>();
|
||||||
|
for (int i = 0; i < Registered.Count; i++)
|
||||||
|
{
|
||||||
|
for (int j = i + 1; j < Registered.Count; j++)
|
||||||
|
{
|
||||||
|
var a = Registered[i];
|
||||||
|
var b = Registered[j];
|
||||||
|
if (a.Key == b.Key && a.Exclusive && b.Exclusive)
|
||||||
|
{
|
||||||
|
conflicts.Add((a, b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conflicts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all currently registered keybind entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of all registered keybind entries.</returns>
|
||||||
|
public static List<KeybindEntry> GetEntries()
|
||||||
|
{
|
||||||
|
return Registered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Inspired by: https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/Keybinds.cs#L29
|
||||||
|
using Rewired;
|
||||||
|
using Rewired.Data;
|
||||||
|
|
||||||
|
namespace MiraAPI.Keybinds
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds helper methods for registering mod keybinds with Rewired.
|
||||||
|
/// </summary>
|
||||||
|
public static class UserDataExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new mod keybind as a user-assignable button action in Rewired.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userData">The Rewired user data to add the action to.</param>
|
||||||
|
/// <param name="actionName">The internal name of the action.</param>
|
||||||
|
/// <param name="description">Text shown in the rebinding UI.</param>
|
||||||
|
/// <param name="key">The default key to assign to this action.</param>
|
||||||
|
/// <param name="category">Category ID to group actions in Rewired (default is 0).</param>
|
||||||
|
/// <param name="elementIdentifierId">The element identifier ID (default is -1, meaning none specified).</param>
|
||||||
|
/// <param name="type">The <see cref="InputActionType"/> for this action (default is Button).</param>
|
||||||
|
/// <returns>The action ID of the newly registered action.</returns>
|
||||||
|
public static int RegisterModBind(this UserData userData, string actionName, string description, KeyboardKeyCode key, int category = 0, int elementIdentifierId = -1, InputActionType type = InputActionType.Button)
|
||||||
|
{
|
||||||
|
userData.AddAction(category);
|
||||||
|
|
||||||
|
var action = userData.GetAction(userData.actions.Count - 1)!;
|
||||||
|
|
||||||
|
action.name = actionName;
|
||||||
|
action.descriptiveName = description;
|
||||||
|
action.categoryId = category;
|
||||||
|
action.type = type;
|
||||||
|
action.userAssignable = true;
|
||||||
|
|
||||||
|
var map = new ActionElementMap
|
||||||
|
{
|
||||||
|
_elementIdentifierId = elementIdentifierId,
|
||||||
|
_actionId = action.id,
|
||||||
|
_elementType = ControllerElementType.Button,
|
||||||
|
_axisContribution = Pole.Positive,
|
||||||
|
_keyboardKeyCode = key,
|
||||||
|
_modifierKey1 = ModifierKey.None,
|
||||||
|
_modifierKey2 = ModifierKey.None,
|
||||||
|
_modifierKey3 = ModifierKey.None,
|
||||||
|
};
|
||||||
|
userData.keyboardMaps[0].actionElementMaps.Add(map);
|
||||||
|
userData.joystickMaps[0].actionElementMaps.Add(map);
|
||||||
|
|
||||||
|
return action.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
using HarmonyLib;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using HarmonyLib;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
using Reactor.Utilities.Extensions;
|
using Reactor.Utilities.Extensions;
|
||||||
|
using Rewired;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
@@ -123,4 +127,22 @@ public static class HudManagerPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(nameof(HudManager.Update))]
|
||||||
|
[HarmonyPostfix]
|
||||||
|
public static void StartPostfix()
|
||||||
|
{
|
||||||
|
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
|
||||||
|
if (PlayerControl.LocalPlayer.Data.IsDead) return;
|
||||||
|
|
||||||
|
foreach (var entry in KeybindManager.GetEntries())
|
||||||
|
{
|
||||||
|
var player = ReInput.players.GetPlayer(0);
|
||||||
|
|
||||||
|
if (player.GetButtonDown(entry.Id))
|
||||||
|
{
|
||||||
|
entry.Handler?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using Rewired;
|
||||||
|
|
||||||
|
namespace MiraAPI.Patches.Keybinds
|
||||||
|
{
|
||||||
|
[HarmonyPatch(typeof(InputManager_Base), nameof(InputManager_Base.Awake))]
|
||||||
|
public static class KeybindMenuPatch
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
private static void StartPrefix(InputManager_Base __instance)
|
||||||
|
{
|
||||||
|
foreach (var entry in KeybindManager.GetEntries())
|
||||||
|
{
|
||||||
|
__instance.userData.RegisterModBind(entry.Id, entry.Description, entry.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,14 @@ using MiraAPI.GameEnd;
|
|||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.GameOptions.Attributes;
|
using MiraAPI.GameOptions.Attributes;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Presets;
|
using MiraAPI.Presets;
|
||||||
using MiraAPI.Roles;
|
using MiraAPI.Roles;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using Reactor.Networking;
|
using Reactor.Networking;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.PluginLoading;
|
namespace MiraAPI.PluginLoading;
|
||||||
|
|
||||||
@@ -129,9 +131,28 @@ public sealed class MiraPluginManager
|
|||||||
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
|
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
|
||||||
|
|
||||||
// Cache all the registered plugins into an array for easy access
|
// Cache all the registered plugins into an array for easy access
|
||||||
RegisteredPlugins = [.._registeredPlugins.Values];
|
RegisteredPlugins = [.. _registeredPlugins.Values];
|
||||||
|
|
||||||
ModifierManager.Modifiers = new ReadOnlyCollection<BaseModifier>(ModifierManager.InternalModifiers);
|
ModifierManager.Modifiers = new ReadOnlyCollection<BaseModifier>(ModifierManager.InternalModifiers);
|
||||||
|
|
||||||
|
foreach (var button in CustomButtonManager.Buttons)
|
||||||
|
{
|
||||||
|
|
||||||
|
KeybindManager.Register($"{button.Name}_Keybind", $"Keybind for {button.Name}", button.CurrentKeybind, button.ClickHandler);
|
||||||
|
|
||||||
|
Logger<MiraApiPlugin>.Info($"Registered keybind for button '{button.GetType().Name}' with default key {button.CurrentKeybind}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var conflicts = KeybindManager.GetConflicts();
|
||||||
|
foreach (var (a, b) in conflicts)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Warning($"Keybind conflict detected: {a.Id} and {b.Id} share {a.Key}");
|
||||||
|
|
||||||
|
var newKey = Helpers.FindAvailableKey(a.Key);
|
||||||
|
b.Key = newKey;
|
||||||
|
|
||||||
|
Logger<MiraApiPlugin>.Info($"Resolved conflict: '{b.Id}' reassigned to {newKey}.");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
using System.Collections;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Il2CppSystem.IO;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using Rewired;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
@@ -19,7 +23,7 @@ public static class Helpers
|
|||||||
/// <returns>A list of alive players.</returns>
|
/// <returns>A list of alive players.</returns>
|
||||||
public static List<PlayerControl> GetAlivePlayers()
|
public static List<PlayerControl> GetAlivePlayers()
|
||||||
{
|
{
|
||||||
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x=>x.Object)];
|
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x => x.Object)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,10 +40,10 @@ public static class Helpers
|
|||||||
case 100:
|
case 100:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
var num = Random.RandomRangeInt(1, 101);
|
var num = UnityEngine.Random.RandomRangeInt(1, 101);
|
||||||
return num <= probability;
|
return num <= probability;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,6 +402,25 @@ public static class Helpers
|
|||||||
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
{
|
{
|
||||||
return new string(Enumerable.Repeat(chars, length)
|
return new string(Enumerable.Repeat(chars, length)
|
||||||
.Select(s => s[Random.RandomRangeInt(0, s.Length)]).ToArray());
|
.Select(s => s[UnityEngine.Random.RandomRangeInt(0, s.Length)]).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds and returns an unused KeyCode that is not equal to the excluded key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exclude">The KeyCode to skip during the search.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The first available KeyCode not currently used by any registered keybind,
|
||||||
|
/// or KeyCode.None if none are available.
|
||||||
|
/// </returns>
|
||||||
|
public static KeyboardKeyCode FindAvailableKey(KeyboardKeyCode exclude)
|
||||||
|
{
|
||||||
|
foreach (KeyboardKeyCode key in Enum.GetValues(typeof(KeyboardKeyCode)))
|
||||||
|
{
|
||||||
|
if (key == exclude) continue;
|
||||||
|
bool used = KeybindManager.GetEntries().Exists(e => e.Key == key);
|
||||||
|
if (!used) return key;
|
||||||
|
}
|
||||||
|
return KeyboardKeyCode.None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user