diff --git a/AmongUs.props b/AmongUs.props index 5ed8991..e0d7f3c 100644 --- a/AmongUs.props +++ b/AmongUs.props @@ -1,8 +1,8 @@  - - + + diff --git a/MiraAPI/GameEnd/CustomGameOver.cs b/MiraAPI/GameEnd/CustomGameOver.cs index 70d7b27..cee076b 100644 --- a/MiraAPI/GameEnd/CustomGameOver.cs +++ b/MiraAPI/GameEnd/CustomGameOver.cs @@ -63,7 +63,7 @@ public abstract class CustomGameOver var reason = GameOverManager.GetGameOverId(); var data = new GameOverData(reason, [.. winners]); - Rpc.Instance.Send(PlayerControl.LocalPlayer, data, true); + Rpc.Instance.Send(PlayerControl.LocalPlayer, data); } /// diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index 93b5b5f..43b6e13 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -122,7 +122,7 @@ public abstract class ModdedOption : IModdedOption entry.Value = Value; } - Rpc.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true); + Rpc.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()]); } OnValueChanged(newValue); 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/Events/KillAnimationPatches.cs b/MiraAPI/Patches/Events/KillAnimationPatches.cs index 5e7b3ef..d8c6336 100644 --- a/MiraAPI/Patches/Events/KillAnimationPatches.cs +++ b/MiraAPI/Patches/Events/KillAnimationPatches.cs @@ -14,13 +14,17 @@ public static class KillAnimationPatches /// /// Used to trigger the . /// - /// The killer. - /// The target. [HarmonyPostfix] - [HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))] - public static void KillAnimPostfix(PlayerControl source, PlayerControl target) + [HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))] + public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance) { - var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId)); + // Checking if state is -1 to wait for the end of coroutine. + if (__instance.__1__state != -1) + { + return; + } + + var afterMurderEvent = new AfterMurderEvent(__instance.source, __instance.target, Helpers.GetBodyById(__instance.target.PlayerId)); MiraEventManager.InvokeEvent(afterMurderEvent); } } diff --git a/MiraAPI/Patches/Events/ReportBodyEventPatch.cs b/MiraAPI/Patches/Events/ReportBodyEventPatch.cs index feba1c9..7b4c60a 100644 --- a/MiraAPI/Patches/Events/ReportBodyEventPatch.cs +++ b/MiraAPI/Patches/Events/ReportBodyEventPatch.cs @@ -1,21 +1,52 @@ -using HarmonyLib; +using AmongUs.InnerNet.GameDataMessages; +using HarmonyLib; +using InnerNet; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting; using MiraAPI.Utilities; +using Reactor.Utilities; namespace MiraAPI.Patches.Events; [HarmonyPatch] public static class ReportBodyEventPatch { - [HarmonyPrefix] - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))] - public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target) + private static bool ReportBodyEventHook(PlayerControl source, NetworkedPlayerInfo? target) { var body = target != null ? Helpers.GetBodyById(target.PlayerId) : null; - var @event = new ReportBodyEvent(__instance, target, body); + var @event = new ReportBodyEvent(source, target, body); MiraEventManager.InvokeEvent(@event); return !@event.IsCancelled; } + + [HarmonyPrefix] + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] + public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target) + { + return ReportBodyEventHook(__instance, target); + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.LateBroadcastReliableMessage))] + public static bool LateBroadcastReliableMessagePrefix(IGameDataMessage rpcMessage) + { + var reportBodyMessage = rpcMessage.TryCast(); + + if (reportBodyMessage == null) + { + return true; + } + + var source = AmongUsClient.Instance.FindObjectByNetId(reportBodyMessage.rpcObjectNetId); + if (source == null) + { + Logger.Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId); + return true; + } + + var target = GameData.Instance.GetPlayerById(reportBodyMessage.targetPlayerId); + + return ReportBodyEventHook(source, target); + } } diff --git a/MiraAPI/Patches/Events/VentEventPatches.cs b/MiraAPI/Patches/Events/VentEventPatches.cs index d1e0476..33e3dbf 100644 --- a/MiraAPI/Patches/Events/VentEventPatches.cs +++ b/MiraAPI/Patches/Events/VentEventPatches.cs @@ -23,10 +23,10 @@ public static class VentEventPatches [HarmonyPrefix] [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.RpcEnterVent))] - public static bool PlayerPhysicsRpcEnterVentPrefix(PlayerPhysics __instance, int id) + public static bool PlayerPhysicsRpcEnterVentPrefix(PlayerPhysics __instance, int ventId) { var pc = __instance.myPlayer; - var vent = ShipStatus.Instance.AllVents.FirstOrDefault(v => v.Id == id); + var vent = ShipStatus.Instance.AllVents.FirstOrDefault(v => v.Id == ventId); var @event = new EnterVentEvent(pc, vent); MiraEventManager.InvokeEvent(@event); @@ -40,10 +40,10 @@ public static class VentEventPatches [HarmonyPrefix] [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.RpcExitVent))] - public static bool PlayerPhysicsRpcExitVentPrefix(PlayerPhysics __instance, int id) + public static bool PlayerPhysicsRpcExitVentPrefix(PlayerPhysics __instance, int ventId) { var pc = __instance.myPlayer; - var vent = ShipStatus.Instance.AllVents.First(v => v.Id == id); + var vent = ShipStatus.Instance.AllVents.First(v => v.Id == ventId); var @event = new ExitVentEvent(pc, vent); MiraEventManager.InvokeEvent(@event); diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index d9ac09b..d04a4d9 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -113,14 +113,16 @@ public static class LobbyViewPanePatches menu.scrollBar.ScrollToTop(); } - [HarmonyPrefix] - [HarmonyPatch(nameof(LobbyViewSettingsPane.SetTab))] - public static bool SetTabPatch(LobbyViewSettingsPane __instance) + [HarmonyPostfix] + [HarmonyPatch(nameof(LobbyViewSettingsPane.ChangeTab))] + [HarmonyPatch(nameof(LobbyViewSettingsPane.RefreshTab))] + // CHANGED BECAUSE OF INLINING + public static void SetTabPatch(LobbyViewSettingsPane __instance) { if (__instance.currentTab != ModifiersTabName || SelectedMod == null) { ModifiersTabButton?.SelectButton(false); - return true; + return; } __instance.taskTabButton.SelectButton(false); @@ -130,7 +132,6 @@ public static class LobbyViewPanePatches var filteredGroups = SelectedMod.OptionGroups .Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true)); DrawOptions(__instance, filteredGroups); - return false; } [HarmonyPrefix] @@ -142,6 +143,11 @@ public static class LobbyViewPanePatches return true; } + if (__instance.currentTab == ModifiersTabName) + { + return false; + } + var filteredGroups = SelectedMod.OptionGroups .Where(x => x is { ShowInModifiersMenu: false, OptionableType: null } && x.GroupVisible.Invoke()); DrawOptions(__instance, filteredGroups); @@ -157,6 +163,11 @@ public static class LobbyViewPanePatches return true; } + if (__instance.currentTab == ModifiersTabName) + { + return false; + } + DrawRolesTab(__instance); return false; } diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index dae6905..8560a77 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -23,10 +23,18 @@ public static class OptionsPatches } [HarmonyPrefix] - [HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.UpdateValue))] + [HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Toggle))] + // UpdateValue was inlined. public static bool ToggleUpdate(ToggleOption __instance) { - return !__instance.IsCustom(); + if (!__instance.IsCustom()) + { + return true; + } + + __instance.CheckMark.enabled = !__instance.CheckMark.enabled; + __instance.OnValueChanged.Invoke(__instance); + return false; } [HarmonyPrefix] diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index a15ce2a..617fc0d 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -300,7 +300,7 @@ public static class RoleSettingMenuPatches if (AmongUsClient.Instance.AmHost) { - Rpc.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()], true); + Rpc.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()]); } GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions; diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index 01d83c2..c93aa6d 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -68,6 +68,7 @@ internal static class PlayerControlPatches [HarmonyPrefix] [HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))] // ReSharper disable once InconsistentNaming + // Note: This method is partially inlined in 2025.5.20 but we already use custom murder RPCs. public static void PlayerControlMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool didSucceed) { var beforeMurderEvent = new BeforeMurderEvent(__instance, target); diff --git a/MiraAPI/Patches/Roles/HauntMenuPatch.cs b/MiraAPI/Patches/Roles/HauntMenuPatch.cs index a7618a6..6f0a0a5 100644 --- a/MiraAPI/Patches/Roles/HauntMenuPatch.cs +++ b/MiraAPI/Patches/Roles/HauntMenuPatch.cs @@ -7,7 +7,8 @@ namespace MiraAPI.Patches.Roles; /// /// Patches the haunt menu to show the actual role name rather then team. /// -[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetFilterText))] +[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetHauntTarget))] +// SetFilterText was INLINED public static class HauntMenuMinigamePatch { public static void Postfix(HauntMenuMinigame __instance) diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs index 57512e3..e52836d 100644 --- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs +++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs @@ -14,6 +14,8 @@ public static class RoleManagerPatches { [HarmonyPrefix] [HarmonyPatch(nameof(RoleManager.SetRole))] + // NOTE: As of 2025.5.20 update, this is INLINED in ONE place: GameManager::ReviveEveryoneFreeplay(void). + // Should not affect normal gameplay, but may cause issues in Freeplay. public static bool SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType) { if (!targetPlayer) diff --git a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs index 49840ee..d867139 100644 --- a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs +++ b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs @@ -1,5 +1,6 @@ using AmongUs.GameOptions; using HarmonyLib; +using Il2CppSystem.Collections.Generic; using MiraAPI.Roles; using Reactor.Utilities; @@ -11,6 +12,23 @@ namespace MiraAPI.Patches.Roles; [HarmonyPatch(typeof(RoleOptionsCollectionV09))] public static class RoleOptionsCollectionPatch { + /// + /// This patch fixes GetNumPerGame being inlined (2025.5.20) in the original code. + /// + [HarmonyPrefix] + [HarmonyPatch(nameof(RoleOptionsCollectionV09.AnyRolesEnabled))] + public static bool AnyRolesEnabledPrefix(RoleOptionsCollectionV09 __instance, ref bool __result) + { + foreach (KeyValuePair keyValuePair in __instance.roles) + { + if (__instance.GetNumPerGame(keyValuePair.Key) > 0) + { + return true; + } + } + return false; + } + /// /// Set the role chance for custom Launchpad roles based on config. /// diff --git a/MiraAPI/Patches/Voting/MeetingHudPatches.cs b/MiraAPI/Patches/Voting/MeetingHudPatches.cs index 2dceb0b..919170d 100644 --- a/MiraAPI/Patches/Voting/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Voting/MeetingHudPatches.cs @@ -205,6 +205,7 @@ internal static class MeetingHudPatches // TODO: figure out a way to do host-authorization since right now any player can send RpcCastVote [HarmonyPrefix] [HarmonyPatch(nameof(MeetingHud.CmdCastVote))] + // Although this method is inlined in MeetingHud.Confirm, the next patch fixes that. public static bool CmdCastVoteOverridePatch(MeetingHud __instance, byte playerId, byte suspectIdx) { VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, playerId, suspectIdx);