From 6b774500ddd0ebd649d1925931078b7bddf2b7a6 Mon Sep 17 00:00:00 2001 From: Atony <75191600+AtonyGit@users.noreply.github.com> Date: Tue, 18 Aug 2026 23:07:39 -0500 Subject: [PATCH] Fix reimplemented Judge for good --- .../Vanilla/Meeting/Voting/HandleVoteEvent.cs | 21 +++++ MiraAPI/MiraApiPlugin.cs | 2 + MiraAPI/Networking/MiraRpc.cs | 5 ++ MiraAPI/Patches/Voting/MeetingHudPatches.cs | 11 +++ MiraAPI/VanillaEvents/JudgeEvents.cs | 8 +- MiraAPI/Voting/VotingUtils.cs | 77 ++++++++++++++++++- 6 files changed, 119 insertions(+), 5 deletions(-) diff --git a/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs b/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs index 2145b32..8f29544 100644 --- a/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs +++ b/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs @@ -34,6 +34,11 @@ public class HandleVoteEvent : MiraCancelableEvent /// public NetworkedPlayerInfo TargetPlayerInfo { get; } + /// + /// Gets or sets a value indicating whether the vote event is an Overrule caused by a Judge. + /// + public bool IsOverruling { get; set; } + /// /// Initializes a new instance of the class. /// @@ -47,4 +52,20 @@ public class HandleVoteEvent : MiraCancelableEvent PreventVote = false; TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId); } + + /// + /// Initializes a new instance of the class. + /// + /// The voter's . + /// The target's playerId. + /// Whether the vote is actually for a Judge. + public HandleVoteEvent(PlayerVoteData playerVoteData, byte targetId, bool isOverruling) + { + VoteData = playerVoteData; + Player = playerVoteData.Owner; + TargetId = targetId; + PreventVote = false; + IsOverruling = isOverruling; + TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId); + } } diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index 594d927..2ce6c88 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -4,6 +4,7 @@ using BepInEx; using BepInEx.Unity.IL2CPP; using HarmonyLib; using MiraAPI.PluginLoading; +using MiraAPI.VanillaEvents; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; @@ -49,6 +50,7 @@ public partial class MiraApiPlugin : BasePlugin public override void Load() { Harmony.PatchAll(); + JudgeEvents.Initialize(); ReactorCredits.Register("Mira API", Version, IsDevBuild, ReactorCredits.AlwaysShow); diff --git a/MiraAPI/Networking/MiraRpc.cs b/MiraAPI/Networking/MiraRpc.cs index 25312e0..7228904 100644 --- a/MiraAPI/Networking/MiraRpc.cs +++ b/MiraAPI/Networking/MiraRpc.cs @@ -74,4 +74,9 @@ public enum MiraRpc : uint /// Custom RPC to replace Among Us CastVote. /// CastVote, + + /// + /// Custom RPC to replace Among Us QueueOverruleVotes. + /// + QueueOverruleVotes, } diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs index ed71003..d88c109 100644 --- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs @@ -2,6 +2,7 @@ using System.Linq; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; +using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting; using MiraAPI.Events.Vanilla.Meeting.Voting; @@ -223,6 +224,16 @@ internal static class MeetingHudPatches return false; } + // TODO: figure out a way to do host-authorization since right now any player can send RpcQueueOverruleVotes + [HarmonyPrefix] + [HarmonyPatch(nameof(MeetingHud.CmdQueueOverruleVotes))] + // Although this method is inlined in MeetingHud.Confirm, the next patch fixes that. + public static bool CmdQueueOverruleVotesPatch(MeetingHud __instance, PlayerId judgePlayerId, PlayerId targetPlayerId, ushort overruleNonce) + { + VotingUtils.RpcQueueOverruleVotes(PlayerControl.LocalPlayer, judgePlayerId.Value, targetPlayerId.Value, overruleNonce); + return false; + } + [HarmonyPrefix] [HarmonyPatch(nameof(MeetingHud.Confirm))] public static bool ConfirmPatch(MeetingHud __instance, [HarmonyArgument(0)] byte suspect) diff --git a/MiraAPI/VanillaEvents/JudgeEvents.cs b/MiraAPI/VanillaEvents/JudgeEvents.cs index 639642e..4a20c6e 100644 --- a/MiraAPI/VanillaEvents/JudgeEvents.cs +++ b/MiraAPI/VanillaEvents/JudgeEvents.cs @@ -6,11 +6,17 @@ namespace MiraAPI.VanillaEvents; public static class JudgeEvents { - [RegisterEvent(100000)] + public static void Initialize() + { + // This is required because MiraAPI isn't an IMiraPlugin (besides in the gamemodes branch) + MiraEventManager.RegisterEventHandler(@event => ProcessVotesEventHandler(@event), -1000); + } + public static void ProcessVotesEventHandler(ProcessVotesEvent @event) { if (MeetingHud.Instance.TryGetWinningOverrule(out var judgeOverrule, out var networkedPlayerInfo, out var networkedPlayerInfo2)) { + Error("Judge has overruled votes!"); @event.OverruledVote = true; @event.OverruledNonce = judgeOverrule.OverruleNonce; diff --git a/MiraAPI/Voting/VotingUtils.cs b/MiraAPI/Voting/VotingUtils.cs index fdbdce1..9744546 100644 --- a/MiraAPI/Voting/VotingUtils.cs +++ b/MiraAPI/Voting/VotingUtils.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting.Voting; using MiraAPI.Networking; @@ -41,9 +42,9 @@ public static class VotingUtils /// The player's . /// Who the player voted for. /// Whether you want the vote to commence or not. - private static void HandleVote(PlayerVoteData voteData, byte suspectIdx, out bool cancelVote) + private static void HandleVote(PlayerVoteData voteData, byte suspectIdx, bool byJudge, out bool cancelVote) { - var @event = new HandleVoteEvent(voteData, suspectIdx); + var @event = new HandleVoteEvent(voteData, suspectIdx, byJudge); MiraEventManager.InvokeEvent(@event); cancelVote = @event.PreventVote; @@ -60,7 +61,14 @@ public static class VotingUtils return; } - voteData.DecreaseRemainingVotes(1); + if (byJudge) + { + voteData.SetRemainingVotes(0); + } + else + { + voteData.DecreaseRemainingVotes(1); + } voteData.VoteForPlayer(suspectIdx); } @@ -100,6 +108,67 @@ public static class VotingUtils MeetingHud.Instance.SkipVoteButton.gameObject.SetActive(true); } + /// + /// Networks the casting of a vote. We replace the vanilla solution with a custom version that works for Judge specifically. + /// + /// The who sent this RPC. + /// The id of the player who casted the vote. + /// The voted player's id. + /// Data that is checked by the Judge role to determine which Judge takes priority. + [MethodRpc((uint)MiraRpc.QueueOverruleVotes)] + public static void RpcQueueOverruleVotes(PlayerControl source, byte srcPlayerId, byte suspectPlayerId, ushort overruleNonce) + { + CustomCastJudgeVote(srcPlayerId, suspectPlayerId, overruleNonce); + } + + private static void CustomCastJudgeVote(PlayerId judgePlayerId, PlayerId targetPlayerId, ushort overruleNonce) + { + var plr = GameData.Instance.GetPlayerById(judgePlayerId.Value); + if (!plr) return; + + var voteData = plr.Object.GetVoteData(); + if (!voteData) return; + + HandleVote(voteData, targetPlayerId.Value, true, out var cancelVote); + + // Handle local behaviour for the voter (for some reason checking AmOwner does not work for host) + if (PlayerControl.LocalPlayer.PlayerId == judgePlayerId.Value) + { + if (!cancelVote) SoundManager.Instance.PlaySound(MeetingHud.Instance.VoteLockinSound, false); + + foreach (var playerVoteArea in MeetingHud.Instance.playerStates) + { + playerVoteArea.ClearButtons(); + } + + MeetingHud.Instance.SkipVoteButton.ClearButtons(); + + var localVoteData = PlayerControl.LocalPlayer.GetVoteData(); + if (!localVoteData || localVoteData.VotesRemaining != 0) return; + + MeetingHud.Instance.SkipVoteButton.VoteComplete = true; + MeetingHud.Instance.SkipVoteButton.gameObject.SetActive(false); + } + + if (cancelVote) return; + + // If player has no more votes, then make it show that the player has used all their votes. + if (voteData.VotesRemaining == 0) + { + MeetingHud.Instance.AddOrUpdateJudgeOverrule(judgePlayerId, targetPlayerId, overruleNonce); + MeetingHud.Instance.playerStates.First(x => x.PlayerId == judgePlayerId.Value).SetVote(targetPlayerId.Value); + } + + // If host, then check end voting, and set votes/send chat if applicable + if (!PlayerControl.LocalPlayer.IsHost()) return; + + MeetingHud.Instance.SetDirtyBit(1U); + MeetingHud.Instance.CheckForEndVoting(); + + if (voteData.VotesRemaining != 0) return; + PlayerControl.LocalPlayer.RpcSendChatNote(judgePlayerId.Value, ChatNoteTypes.DidVote); + } + /// /// Networks the casting of a vote. We replace the vanilla solution with a custom version that works for the use case. /// @@ -120,7 +189,7 @@ public static class VotingUtils var voteData = plr.Object.GetVoteData(); if (!voteData) return; - HandleVote(voteData, suspectPlayerId, out var cancelVote); + HandleVote(voteData, suspectPlayerId, false, out var cancelVote); // Handle local behaviour for the voter (for some reason checking AmOwner does not work for host) if (PlayerControl.LocalPlayer.PlayerId == srcPlayerId)