diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index 4edd4f8..726c16f 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 c0acc20..89e722b 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 02fc908..58d8bee 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 c4af2ca..78beb0b 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 0000000..809ce11 --- /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 78fa114..ffaa315 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -57,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; @@ -126,7 +126,17 @@ public abstract class CustomActionButton /// /// Gets the gameObject used for the keybind icon. /// - public GameObject KeybindIcon { get; set; } + 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. @@ -200,12 +210,18 @@ public abstract class CustomActionButton } })); - KeybindIcon = Object.Instantiate(Button.usesRemainingSprite.gameObject, Button.transform); - KeybindIcon.GetComponent().sprite = MiraAssets.KeybindButton.LoadAsset(); - KeybindIcon.transform.GetChild(0).GetComponent().text = - KeybindManager.GetEntries().First(x => x.Id == $"{Name}_Keybind").Key.ToString(); - KeybindIcon.name = "KeybindIcon"; - KeybindIcon.transform.localPosition = new(0.341f, 0.374f, -0.1f); + 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); + } } /// @@ -464,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) diff --git a/MiraAPI/Hud/CustomButtonManager.cs b/MiraAPI/Hud/CustomButtonManager.cs index e576595..8e57c40 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 309a7d2..9c6f741 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 4a7bc30..209afcd 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 acb4482..fa848de 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 af7ce8c..41217e6 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 02e9102..759a0fd 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/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 863df73..e5f991f 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 2bf7b37..6c0bf4c 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 159b576..38fbf16 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);