diff --git a/Directory.Build.props b/Directory.Build.props
index f6b4be75..c8f52ba8 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,7 +8,7 @@
true
- 0.2.0
+ 0.2.1
dev
All Of Us, XtraCube, Angxl
diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
index 4edd4f82..726c16ff 100644
--- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
+++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
@@ -17,10 +17,8 @@ public class FreezeButton : CustomActionButton
public override float Cooldown => OptionGroupSingleton.Instance.FreezeDuration;
- public override int MaxUses => (int)OptionGroupSingleton.Instance.FreezeUses;
-
public override LoadableAsset Sprite => ExampleAssets.ExampleButton;
- public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.T;
+ public override KeyboardKeyCode? DefaultKeybind => KeyboardKeyCode.T;
public override ModifierKey Modifier1 => ModifierKey.Control;
protected override void OnClick()
diff --git a/MiraAPI.Example/Buttons/MeetingButton.cs b/MiraAPI.Example/Buttons/MeetingButton.cs
index c0acc206..89e722b0 100644
--- a/MiraAPI.Example/Buttons/MeetingButton.cs
+++ b/MiraAPI.Example/Buttons/MeetingButton.cs
@@ -16,7 +16,7 @@ public class MeetingButton : CustomActionButton
public override int MaxUses => 3;
public override LoadableAsset Sprite => ExampleAssets.ExampleButton;
- public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.P;
+ public override KeyboardKeyCode? DefaultKeybind => KeyboardKeyCode.P;
public override ModifierKey Modifier1 => ModifierKey.Control;
public override ModifierKey Modifier2 => ModifierKey.Shift;
diff --git a/MiraAPI.Example/Buttons/NeutralKillerButton.cs b/MiraAPI.Example/Buttons/NeutralKillerButton.cs
index 02fc9086..58d8beea 100644
--- a/MiraAPI.Example/Buttons/NeutralKillerButton.cs
+++ b/MiraAPI.Example/Buttons/NeutralKillerButton.cs
@@ -13,7 +13,7 @@ public class NeutralKillerButton : CustomActionButton
public override string Name => "Win Game";
public override float Cooldown => 0f;
public override LoadableAsset Sprite => ExampleAssets.ExampleButton;
- public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.K;
+ public override KeyboardKeyCode? DefaultKeybind => KeyboardKeyCode.K;
protected override void OnClick()
{
CustomGameOver.Trigger([PlayerControl.LocalPlayer.Data]);
diff --git a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs
index c4af2ca7..78beb0b9 100644
--- a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs
+++ b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs
@@ -22,7 +22,7 @@ public class TeleportButton : CustomActionButton
public override LoadableAsset Sprite => ExampleAssets.TeleportButton;
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
public static bool IsZoom { get; private set; }
- public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.M;
+ public override KeyboardKeyCode? DefaultKeybind => KeyboardKeyCode.M;
public override bool Enabled(RoleBehaviour? role)
{
return role is TeleporterRole;
diff --git a/MiraAPI/Events/Vanilla/Gameplay/IntroRoleRevealEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/IntroRoleRevealEvent.cs
new file mode 100644
index 00000000..809ce116
--- /dev/null
+++ b/MiraAPI/Events/Vanilla/Gameplay/IntroRoleRevealEvent.cs
@@ -0,0 +1,21 @@
+namespace MiraAPI.Events.Vanilla.Gameplay;
+
+///
+/// The event that is invoked when the player's role is shown on the intro cutscene. Non cancelable.
+///
+public class IntroRoleRevealEvent : MiraEvent
+{
+ ///
+ /// Gets the instance of the IntroCutscene.
+ ///
+ public IntroCutscene IntroCutscene { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The intro cutscene.
+ public IntroRoleRevealEvent(IntroCutscene introCutscene)
+ {
+ IntroCutscene = introCutscene;
+ }
+}
diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs
index ac48c4ae..ffaa315b 100644
--- a/MiraAPI/Hud/CustomActionButton.cs
+++ b/MiraAPI/Hud/CustomActionButton.cs
@@ -1,11 +1,14 @@
using System;
using System.Globalization;
+using System.Linq;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
+using MiraAPI.Keybinds;
using MiraAPI.Patches;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Rewired;
+using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
@@ -54,23 +57,23 @@ public abstract class CustomActionButton
public virtual int MaxUses => 0;
///
- /// Gets or sets the current key binding for this button.
+ /// Gets the current key binding for this button. If null, no keybind will be added.
///
- public abstract KeyboardKeyCode Defaultkeybind { get; }
+ public virtual KeyboardKeyCode? DefaultKeybind { get; }
///
- /// The first optional modifier key (e.g., Control, Shift, Alt) that must be held with the main key to activate the button.
+ /// Gets the first optional modifier key (e.g., Control, Shift, Alt) that must be held with the main key to activate the button.
///
public virtual ModifierKey Modifier1 => ModifierKey.None;
///
- /// The second optional modifier key.
+ /// Gets the second optional modifier key.
/// Set to if unused.
///
public virtual ModifierKey Modifier2 => ModifierKey.None;
///
- /// The third optional modifier key.
+ /// Gets the third optional modifier key.
/// Set to if unused.
///
public virtual ModifierKey Modifier3 => ModifierKey.None;
@@ -120,6 +123,21 @@ public abstract class CustomActionButton
///
public ActionButton? Button { get; set; }
+ ///
+ /// Gets the gameObject used for the keybind icon.
+ ///
+ public GameObject? KeybindIcon { get; private set; }
+
+ ///
+ /// Gets or sets the keybind entry for the button.
+ ///
+ private KeybindManager.KeybindEntry? KeybindEntry { get; set; }
+
+ ///
+ /// Gets or sets the keybind icon text.
+ ///
+ private TextMeshPro? KeybindText { get; set; }
+
///
/// The method used to create the button.
///
@@ -191,6 +209,19 @@ public abstract class CustomActionButton
}
}
}));
+
+ if (DefaultKeybind != KeyboardKeyCode.None)
+ {
+ KeybindEntry = KeybindManager.GetEntries().First(x => x.Id == $"{Name}_Keybind");
+ KeybindIcon =
+ Helpers.CreateKeybindIcon(
+ Button.gameObject,
+ KeybindEntry.Key,
+ new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
+ );
+ KeybindText = KeybindIcon.transform.GetChild(0).GetComponent();
+ Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
+ }
}
///
@@ -449,6 +480,11 @@ public abstract class CustomActionButton
/// The local PlayerControl.
public virtual void FixedUpdateHandler(PlayerControl playerControl)
{
+ if (KeybindText != null && KeybindEntry != null)
+ {
+ KeybindText.text = KeybindEntry.Key.ToString();
+ }
+
if (Timer >= 0)
{
if (!TimerPaused)
@@ -558,4 +594,4 @@ public abstract class CustomActionButton : CustomActionButton where T : MonoB
Target = null;
SetOutline(false);
}
-}
+}
\ No newline at end of file
diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs
index e576595e..8e57c400 100644
--- a/MiraAPI/Hud/CustomButtonManager.cs
+++ b/MiraAPI/Hud/CustomButtonManager.cs
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Reflection;
using MiraAPI.Events.Mira;
using MiraAPI.PluginLoading;
+using UnityEngine;
namespace MiraAPI.Hud;
diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs
index 309a7d21..9c6f7416 100644
--- a/MiraAPI/Modifiers/ModifierExtensions.cs
+++ b/MiraAPI/Modifiers/ModifierExtensions.cs
@@ -63,7 +63,7 @@ public static class ModifierExtensions
///
/// The player to remove the modifier from.
/// The unique ID of the modifier.
- [MethodRpc((uint)MiraRpc.RemoveModifier, SendImmediately = true)]
+ [MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
{
target.RemoveModifier(uniqueId);
diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs
index 4a7bc30a..209afcd0 100644
--- a/MiraAPI/Networking/CustomMurderRpc.cs
+++ b/MiraAPI/Networking/CustomMurderRpc.cs
@@ -29,7 +29,7 @@ public static class CustomMurderRpc
/// Should the killer be snapped to the dead player.
/// Should the kill animation be shown.
/// Should the kill sound be played.
- [MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before, SendImmediately = true)]
+ [MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs
index acb44826..fa848dec 100644
--- a/MiraAPI/Patches/HudManagerPatches.cs
+++ b/MiraAPI/Patches/HudManagerPatches.cs
@@ -1,11 +1,14 @@
using System;
using System.Collections;
+using System.Collections.Generic;
using HarmonyLib;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
+using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Rewired;
+using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -22,6 +25,8 @@ public static class HudManagerPatches
public static Transform? BottomRight { get; private set; }
public static Transform? Buttons { get; private set; }
+ private static Dictionary VanillaKeybindIcons = new();
+
/*
///
/// Trigger hudstart on current custom gamemode
@@ -96,6 +101,26 @@ public static class HudManagerPatches
gridArrange.ArrangeChilds();
aspectPosition.AdjustPosition();
+
+ var keybindIconPos = new Vector3(-0.4f, 0.4f, -9f);
+ var vanillaButtons = new Dictionary
+ {
+ { __instance.KillButton.gameObject, 8 },
+ { __instance.UseButton.gameObject, 6 },
+ { __instance.ReportButton.gameObject, 7 },
+ { __instance.ImpostorVentButton.gameObject, 50 },
+ { __instance.SabotageButton.gameObject, 4 },
+ };
+
+ foreach (var kvp in vanillaButtons)
+ {
+ var buttonObj = kvp.Key;
+ var actionId = kvp.Value;
+
+ var key = Helpers.GetKeybindByActionId(actionId);
+ var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos);
+ VanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent(), actionId);
+ }
}
///
@@ -132,8 +157,10 @@ public static class HudManagerPatches
[HarmonyPostfix]
public static void StartPostfix()
{
- if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
- if (PlayerControl.LocalPlayer.Data.IsDead) return;
+ foreach (var btnIcon in VanillaKeybindIcons)
+ {
+ btnIcon.Key.text = Helpers.GetKeybindByActionId(btnIcon.Value).ToString();
+ }
foreach (var entry in KeybindManager.GetEntries())
{
diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs
index af7ce8c4..41217e6e 100644
--- a/MiraAPI/Patches/IntroCutscenePatches.cs
+++ b/MiraAPI/Patches/IntroCutscenePatches.cs
@@ -29,6 +29,16 @@ public static class IntroCutscenePatches
MiraEventManager.InvokeEvent(@event);
}
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(IntroCutscene._ShowRole_d__41), nameof(IntroCutscene._ShowRole_d__41.MoveNext))]
+ [HarmonyPriority(Priority.Last)]
+ public static void ShowRolePatch(IntroCutscene._ShowRole_d__41 __instance)
+ {
+ var realInstance = __instance.__4__this;
+ var @event = new IntroRoleRevealEvent(realInstance);
+ MiraEventManager.InvokeEvent(@event);
+ }
+
[HarmonyPrefix]
[HarmonyPatch(nameof(IntroCutscene.BeginImpostor))]
[HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))]
diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs
index 02e91022..759a0fd3 100644
--- a/MiraAPI/PluginLoading/MiraPluginManager.cs
+++ b/MiraAPI/PluginLoading/MiraPluginManager.cs
@@ -18,6 +18,7 @@ using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking;
using Reactor.Utilities;
+using Rewired;
using UnityEngine;
namespace MiraAPI.PluginLoading;
@@ -137,9 +138,13 @@ public sealed class MiraPluginManager
foreach (var button in CustomButtonManager.Buttons)
{
- KeybindManager.Register($"{button.Name}_Keybind", $"Keybind for {button.Name}", button.Defaultkeybind, button.ClickHandler, modifier1: button.Modifier1, modifier2: button.Modifier2, modifier3: button.Modifier3);
+ if (button.DefaultKeybind == null)
+ {
+ continue;
+ }
+ KeybindManager.Register($"{button.Name}_Keybind", $"Keybind for {button.Name}", (KeyboardKeyCode)button.DefaultKeybind, button.ClickHandler, modifier1: button.Modifier1, modifier2: button.Modifier2, modifier3: button.Modifier3);
- Logger.Info($"Registered keybind for button '{button.GetType().Name}' with default key {button.Defaultkeybind}.");
+ Logger.Info($"Registered keybind for button '{button.GetType().Name}' with default key {(KeyboardKeyCode)button.DefaultKeybind}.");
}
};
}
diff --git a/MiraAPI/Resources/KeybindButton.png b/MiraAPI/Resources/KeybindButton.png
new file mode 100644
index 00000000..e537b3b5
Binary files /dev/null and b/MiraAPI/Resources/KeybindButton.png differ
diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs
index 1bd130a1..148c65a3 100644
--- a/MiraAPI/Utilities/Assets/MiraAssets.cs
+++ b/MiraAPI/Utilities/Assets/MiraAssets.cs
@@ -94,4 +94,9 @@ public static class MiraAssets
/// Gets the white CategoryHeader used in the Settings Menu.
///
public static LoadableResourceAsset CategoryHeader { get; } = new("MiraAPI.Resources.CategoryHeader.png");
+
+ ///
+ /// Gets the sprite used for the Keybind icons.
+ ///
+ public static LoadableResourceAsset KeybindButton { get; } = new("MiraAPI.Resources.KeybindButton.png");
}
diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs
index 863df730..e5f991f0 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -94,7 +94,7 @@ public static class Extensions
/// The current timer value.
/// The maximum timer value.
/// The format string to use for the timer text.
- public static void SetCooldownFormat(this ActionButton button, float timer, float maxTimer, string format = "0")
+ public static void SetCooldownFormat(this ActionButton? button, float timer, float maxTimer, string format = "0")
{
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
button.isCoolingDown = num > 0f;
diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs
index 2bf7b37a..6c0bf4cd 100644
--- a/MiraAPI/Utilities/Helpers.cs
+++ b/MiraAPI/Utilities/Helpers.cs
@@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using Il2CppSystem.IO;
using MiraAPI.Keybinds;
+using MiraAPI.Utilities.Assets;
using Rewired;
using TMPro;
using UnityEngine;
@@ -25,6 +26,27 @@ public static class Helpers
{
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x => x.Object)];
}
+ internal static GameObject CreateKeybindIcon(GameObject button, KeyboardKeyCode keyCode, Vector3 localPos)
+ {
+ var keybindIcon = Object.Instantiate(HudManager.Instance.AbilityButton.usesRemainingSprite.gameObject, button.transform);
+ keybindIcon.GetComponent().sprite = MiraAssets.KeybindButton.LoadAsset();
+ keybindIcon.transform.GetChild(0).GetComponent().text = keyCode.ToString();
+ keybindIcon.name = "KeybindIcon";
+ keybindIcon.transform.localPosition = localPos;
+ return keybindIcon;
+ }
+
+ ///
+ /// Gets the keybind for an action with ReInput.
+ ///
+ /// The action ID.
+ /// The keyboard key code.
+ public static KeyboardKeyCode GetKeybindByActionId(int actionId)
+ {
+ var player = ReInput.players.GetPlayer(0);
+ return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false)
+ .keyboardKeyCode;
+ }
///
/// Determines whether a given probability check succeeds.
diff --git a/MiraAPI/Voting/VotingUtils.cs b/MiraAPI/Voting/VotingUtils.cs
index 159b5762..38fbf166 100644
--- a/MiraAPI/Voting/VotingUtils.cs
+++ b/MiraAPI/Voting/VotingUtils.cs
@@ -71,7 +71,7 @@ public static class VotingUtils
/// The player who is sending the RPC. Should be the host.
/// The player who voted.
/// The player who the voter voted for.
- [MethodRpc((uint)MiraRpc.RemoveVote, SendImmediately = true)]
+ [MethodRpc((uint)MiraRpc.RemoveVote)]
public static void RpcRemoveVote(PlayerControl source, byte voterId, byte votedFor)
{
if (!source.IsHost()) return;
@@ -107,7 +107,7 @@ public static class VotingUtils
/// The player who sent this RPC.
/// The id of the player who casted the vote.
/// The voted player's id.
- [MethodRpc((uint)MiraRpc.CastVote, SendImmediately = true)]
+ [MethodRpc((uint)MiraRpc.CastVote)]
public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
{
CustomCastVote(srcPlayerId, suspectPlayerId);