diff --git a/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs b/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs
index 93005de..48ec40f 100644
--- a/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs
+++ b/MiraAPI/Events/Vanilla/Meeting/Voting/HandleVoteEvent.cs
@@ -24,6 +24,11 @@ public class HandleVoteEvent : MiraCancelableEvent
///
public byte TargetId { get; }
+ ///
+ /// Gets a value indicating whether to prevent the vote from commencing.
+ ///
+ public bool PreventVote { get; }
+
///
/// Gets the player info of the target.
///
@@ -39,6 +44,7 @@ public class HandleVoteEvent : MiraCancelableEvent
VoteData = playerVoteData;
Player = playerVoteData.Owner;
TargetId = targetId;
+ PreventVote = false;
TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId);
}
}
diff --git a/MiraAPI/Networking/MiraRpc.cs b/MiraAPI/Networking/MiraRpc.cs
index 9ee1088..29703d7 100644
--- a/MiraAPI/Networking/MiraRpc.cs
+++ b/MiraAPI/Networking/MiraRpc.cs
@@ -44,4 +44,9 @@ public enum MiraRpc : uint
/// Custom RPC to undo a player's vote.
///
RemoveVote,
+
+ ///
+ /// Custom RPC to replace Among Us CastVote
+ ///
+ CastVote,
}
diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs
index 316fc7f..809485e 100644
--- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs
+++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs
@@ -8,6 +8,7 @@ using MiraAPI.Events.Vanilla.Meeting.Voting;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Voting;
+using Reactor.Utilities;
namespace MiraAPI.Patches.Voting;
@@ -140,84 +141,6 @@ internal static class MeetingHudPatches
return false;
}
- [HarmonyPrefix]
- [HarmonyPatch(nameof(MeetingHud.CastVote))]
- public static bool CastVotePatch(MeetingHud __instance, byte srcPlayerId, byte suspectPlayerId)
- {
- var plr = GameData.Instance.GetPlayerById(srcPlayerId);
-
- if (plr == null)
- {
- return false;
- }
-
- var voteData = plr.Object.GetVoteData();
- if (voteData == null || voteData.VotesRemaining == 0 ||
- voteData.VotedFor(suspectPlayerId))
- {
- return false;
- }
-
- VotingUtils.HandleVote(voteData, suspectPlayerId);
-
- __instance.SetDirtyBit(1U);
- __instance.CheckForEndVoting();
-
- if (voteData.VotesRemaining != 0)
- {
- return false;
- }
-
- __instance.playerStates.First(x => x.TargetPlayerId == srcPlayerId).SetVote(suspectPlayerId);
- PlayerControl.LocalPlayer.RpcSendChatNote(srcPlayerId, ChatNoteTypes.DidVote);
-
- return false;
- }
-
- [HarmonyPostfix]
- [HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
- public static void CmdCastVotePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
- {
- var plr = GameData.Instance.GetPlayerById(playerId);
- var voteData = plr.Object.GetVoteData();
-
- if (!AmongUsClient.Instance.AmHost)
- {
- if (voteData == null || voteData.VotesRemaining == 0 ||
- voteData.VotedFor(suspectIdx))
- {
- return;
- }
-
- VotingUtils.HandleVote(voteData, suspectIdx);
-
- if (voteData.VotesRemaining == 0)
- {
- __instance.playerStates.First(x => x.TargetPlayerId == playerId).SetVote(suspectIdx);
- }
- }
-
- if (PlayerControl.LocalPlayer.PlayerId != playerId)
- {
- return;
- }
-
- SoundManager.Instance.PlaySound(__instance.VoteLockinSound, false);
-
- foreach (var playerVoteArea in __instance.playerStates)
- {
- playerVoteArea.ClearButtons();
- }
-
- __instance.SkipVoteButton.ClearButtons();
-
- var localVoteData = PlayerControl.LocalPlayer.GetVoteData();
- if (localVoteData == null || localVoteData.VotesRemaining != 0) return;
-
- __instance.SkipVoteButton.voteComplete = true;
- __instance.SkipVoteButton.gameObject.SetActive(false);
- }
-
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.CheckForEndVoting))]
public static bool EndCheck(MeetingHud __instance)
@@ -280,12 +203,20 @@ internal static class MeetingHudPatches
return false;
}
+ // TODO: figure out a way to do host-authorization since right now any player can send RpcCastVote
+ [HarmonyPrefix]
+ [HarmonyPatch(nameof(MeetingHud.CmdCastVote))]
+ public static bool CmdCastVoteOverridePatch(MeetingHud __instance, byte playerId, byte suspectIdx)
+ {
+ VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, playerId, suspectIdx);
+ return false;
+ }
+
[HarmonyPrefix]
[HarmonyPatch(nameof(MeetingHud.Confirm))]
public static bool ConfirmPatch(MeetingHud __instance, [HarmonyArgument(0)] byte suspect)
{
__instance.CmdCastVote(PlayerControl.LocalPlayer.PlayerId, suspect);
-
return false;
}
}
diff --git a/MiraAPI/Voting/VotingUtils.cs b/MiraAPI/Voting/VotingUtils.cs
index 56833a6..38fbf16 100644
--- a/MiraAPI/Voting/VotingUtils.cs
+++ b/MiraAPI/Voting/VotingUtils.cs
@@ -41,16 +41,26 @@ public static class VotingUtils
///
/// The player's vote data.
/// Who the player voted for.
- public static void HandleVote(PlayerVoteData voteData, byte suspectIdx)
+ /// Whether you want the vote to commence or not.
+ private static void HandleVote(PlayerVoteData voteData, byte suspectIdx, out bool cancelVote)
{
var @event = new HandleVoteEvent(voteData, suspectIdx);
MiraEventManager.InvokeEvent(@event);
+ cancelVote = @event.PreventVote;
+
if (@event.IsCancelled)
{
return;
}
+ if (voteData.VotesRemaining == 0 ||
+ voteData.VotedFor(suspectIdx))
+ {
+ cancelVote = true;
+ return;
+ }
+
voteData.DecreaseRemainingVotes(1);
voteData.VoteForPlayer(suspectIdx);
}
@@ -64,10 +74,7 @@ public static class VotingUtils
[MethodRpc((uint)MiraRpc.RemoveVote)]
public static void RpcRemoveVote(PlayerControl source, byte voterId, byte votedFor)
{
- if (source.OwnerId != AmongUsClient.Instance.HostId)
- {
- return;
- }
+ if (!source.IsHost()) return;
MeetingHud.Instance.playerStates.First(state => state.TargetPlayerId == voterId).UnsetVote();
@@ -94,6 +101,65 @@ 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 the use case.
+ ///
+ /// The player who sent this RPC.
+ /// The id of the player who casted the vote.
+ /// The voted player's id.
+ [MethodRpc((uint)MiraRpc.CastVote)]
+ public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
+ {
+ CustomCastVote(srcPlayerId, suspectPlayerId);
+ }
+
+ private static void CustomCastVote(byte srcPlayerId, byte suspectPlayerId)
+ {
+ var plr = GameData.Instance.GetPlayerById(srcPlayerId);
+ if (!plr) return;
+
+ var voteData = plr.Object.GetVoteData();
+ if (!voteData) return;
+
+ HandleVote(voteData, suspectPlayerId, out var cancelVote);
+
+ // Handle local behaviour for the voter (for some reason checking AmOwner does not work for host)
+ if (PlayerControl.LocalPlayer.PlayerId == srcPlayerId)
+ {
+ 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.playerStates.First(x => x.TargetPlayerId == srcPlayerId).SetVote(suspectPlayerId);
+ }
+
+ // 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(srcPlayerId, ChatNoteTypes.DidVote);
+ }
+
///
/// Calculates the total number of votes.
///