From 13a5564c0415abddc15fcfdb627c07c0c88f3cc9 Mon Sep 17 00:00:00 2001 From: D1GQ Date: Wed, 18 Mar 2026 21:41:51 -0500 Subject: [PATCH] add DisableSabotagesForDead and remove private only lobbies --- src/Data/Config/BAUConfigs.cs | 13 +- src/Managers/NetworkManager.cs | 41 ++++- src/Modules/AntiCheat/BetterAntiCheat.cs | 26 +-- .../RPCHandlers/UpdateSystemHandler.cs | 29 +++- src/Modules/GameState.cs | 7 - src/Patches/Client/PrivateLobbyPatch.cs | 150 ------------------ .../Gameplay/UI/Settings/GameSettingsPatch.cs | 11 +- src/Resources/Lang/en_US.json | 1 + 8 files changed, 87 insertions(+), 191 deletions(-) delete mode 100644 src/Patches/Client/PrivateLobbyPatch.cs diff --git a/src/Data/Config/BAUConfigs.cs b/src/Data/Config/BAUConfigs.cs index a3712e6..d02e544 100644 --- a/src/Data/Config/BAUConfigs.cs +++ b/src/Data/Config/BAUConfigs.cs @@ -73,11 +73,6 @@ internal static class BAUConfigs /// internal static BAUConfigEntry CommandPrefix { get; } = new("Mod", "CommandPrefix", "/"); - /// - /// Gets the configuration entry for private only lobby setting. - /// - internal static BAUConfigEntry PrivateOnlyLobby { get; } = new("Mod", "PrivateOnlyLobby", false); - /// /// Gets the configuration entry for favorite color setting. /// @@ -94,10 +89,10 @@ internal static class BAUConfigs internal static void LoadConfigs() { BAUModdedSupportEvents.InvokeAll_OnBAUConfigEntriesLoaded([ - PrivateOnlyLobby, AntiCheat, SendBetterRpc, - BetterNotifications, ForceOwnLanguage, ChatDarkMode, - ChatInGameplay, LobbyPlayerInfo, DisableLobbyTheme, - UnlockFPS, ShowFPS, MinimapIcons, VentColorGroups, CommandPrefix, + AntiCheat, SendBetterRpc, BetterNotifications, + ForceOwnLanguage, ChatDarkMode, ChatInGameplay, LobbyPlayerInfo, + DisableLobbyTheme, UnlockFPS, ShowFPS, + MinimapIcons, VentColorGroups, CommandPrefix, FavoriteColor, SettingsPreset ]); diff --git a/src/Managers/NetworkManager.cs b/src/Managers/NetworkManager.cs index 5854acb..863f3ce 100644 --- a/src/Managers/NetworkManager.cs +++ b/src/Managers/NetworkManager.cs @@ -462,7 +462,18 @@ internal static class NetworkManager BetterAntiCheat.HandleCheatRPCBeforeCheck(player, callId, reader); - if (BetterAntiCheat.CheckCancelRPC(player, callId, reader) != true) + if (GameState.IsHost) + { + var tempReader = MessageReader.Get(reader); + if (RPCHandler.HandleRPC(callId, player, tempReader, HandlerFlag.BetterHost) == false) + { + tempReader.Recycle(); + return false; + } + tempReader.Recycle(); + } + + if (BetterAntiCheat.CheckCancelRPC(player, callId, reader) == false) { if (!player.IsLocalPlayer()) Logger_.LogCheat($"RPC canceled by Anti-Cheat: {Enum.GetName((RpcCalls)callId)}{Enum.GetName((CustomRPC)callId)} - {callId}"); return false; @@ -474,10 +485,32 @@ internal static class NetworkManager return true; } - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] + + [HarmonyPatch] internal static class MessageReaderUpdateSystemPatch { - internal static bool Prefix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(byte))] + [HarmonyPrefix] + internal static bool ShipStatus_UpdateSystemLocal_Prefix(SystemTypes systemType, PlayerControl player, byte amount) + { + MessageWriter messageWriter = MessageWriter.Get(0); + messageWriter.Write(amount); + MessageReader messageReader = MessageReader.Get(messageWriter.ToByteArray(false)); + messageWriter.Recycle(); + + if (BetterAntiCheat.RpcUpdateSystemCheck(player, systemType, messageReader) != true) + { + messageReader.Recycle(); + return false; + } + messageReader.Recycle(); + + return true; + } + + [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] + [HarmonyPrefix] + internal static bool ShipStatus_UpdateSystem_Prefix(SystemTypes systemType, PlayerControl player, MessageReader msgReader) { player.BetterData().AntiCheatInfo.RPCSentPS++; if (player.BetterData().AntiCheatInfo.RPCSentPS >= ExtendedAntiCheatInfo.MAX_RPC_SENT) @@ -485,7 +518,7 @@ internal static class NetworkManager return false; } - if (BetterAntiCheat.RpcUpdateSystemCheck(player, systemType, reader) != true) return false; + if (BetterAntiCheat.RpcUpdateSystemCheck(player, systemType, msgReader) != true) return false; return true; } diff --git a/src/Modules/AntiCheat/BetterAntiCheat.cs b/src/Modules/AntiCheat/BetterAntiCheat.cs index 5e6cc43..004b293 100644 --- a/src/Modules/AntiCheat/BetterAntiCheat.cs +++ b/src/Modules/AntiCheat/BetterAntiCheat.cs @@ -266,24 +266,24 @@ internal static class BetterAntiCheat /// True if the sabotage should be allowed, false otherwise. internal static bool RpcUpdateSystemCheck(PlayerControl player, SystemTypes systemType, MessageReader oldReader) { - if (Utils.SystemTypeIsSabotage(systemType) || systemType is SystemTypes.Doors) - { - if (GameState.IsPrivateOnlyLobby && BetterGameSettings.DisableSabotages.GetBool()) return false; - } - MessageReader reader = MessageReader.Get(oldReader); - RegisterRPCHandlerAttribute.GetClassInstance().CatchedSystemType = systemType; - bool notCanceled = RPCHandler.HandleRPC((byte)RpcCalls.UpdateSystem, player, reader, HandlerFlag.AntiCheatCancel); - if (!notCanceled) + if (!RPCHandler.HandleRPC((byte)RpcCalls.UpdateSystem, player, reader, HandlerFlag.AntiCheatCancel)) { - var tempReader = MessageReader.Get(reader); - Logger_.LogCheat($"RPC canceled by Anti-Cheat: {Enum.GetName(typeof(SystemTypes), (int)systemType)} - {tempReader.ReadByte()}"); - tempReader.Recycle(); + reader.Recycle(); + return false; } - reader.Recycle(); - return notCanceled; + + MessageReader reader2 = MessageReader.Get(oldReader); + if (!RPCHandler.HandleRPC((byte)RpcCalls.UpdateSystem, player, reader2, HandlerFlag.BetterHost)) + { + reader2.Recycle(); + return false; + } + reader2.Recycle(); + + return true; } /// diff --git a/src/Modules/AntiCheat/RPCHandlers/UpdateSystemHandler.cs b/src/Modules/AntiCheat/RPCHandlers/UpdateSystemHandler.cs index 9e7e84d..00812a9 100644 --- a/src/Modules/AntiCheat/RPCHandlers/UpdateSystemHandler.cs +++ b/src/Modules/AntiCheat/RPCHandlers/UpdateSystemHandler.cs @@ -1,5 +1,6 @@ using BetterAmongUs.Attributes; using BetterAmongUs.Helpers; +using BetterAmongUs.Patches.Gameplay.UI.Settings; using Hazel; using UnityEngine; @@ -51,9 +52,8 @@ internal sealed class UpdateSystemHandler : RPCHandler internal override bool HandleAntiCheatCancel(PlayerControl? sender, MessageReader reader) { - if (GameState.IsHost && sender.IsHost()) return true; + if (sender.IsHost()) return true; - MessageReader oldReader = MessageReader.Get(reader); byte count = reader.ReadByte(); if (ShipStatus.Instance.Systems.TryGetValue(CatchedSystemType, out ISystemType system)) @@ -62,12 +62,31 @@ internal sealed class UpdateSystemHandler : RPCHandler if (systemHandlers.TryGetValue(systemKey, out var handler)) { - oldReader.Recycle(); - return handler.Invoke(sender, system, oldReader, count); + return handler.Invoke(sender, system, reader, count); } } - oldReader.Recycle(); + return true; + } + + internal override bool BetterHandle(PlayerControl? sender, MessageReader reader) + { + // Prevent dead impostors from sabotaging if setting is enabled + if (CatchedSystemType is SystemTypes.Sabotage or SystemTypes.Electrical or SystemTypes.LifeSupp or + SystemTypes.Reactor or SystemTypes.Laboratory or SystemTypes.HeliSabotage or + SystemTypes.Comms or SystemTypes.MushroomMixupSabotage) + { + if (BetterGameSettings.DisableSabotages.GetBool()) + return false; + + if (BetterGameSettings.DisableSabotagesForDead.GetBool()) + { + if (!sender.IsAlive()) + { + return false; + } + } + } return true; } diff --git a/src/Modules/GameState.cs b/src/Modules/GameState.cs index 1b4b2e8..e6af1d3 100644 --- a/src/Modules/GameState.cs +++ b/src/Modules/GameState.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using BetterAmongUs.Data.Config; namespace BetterAmongUs.Modules; @@ -321,12 +320,6 @@ internal static class GameState /// internal static bool IsHost => AmongUsClient.Instance != null && AmongUsClient.Instance.AmHost; - /// - /// Gets whether the lobby is private-only (requires specific settings). - /// - internal static bool IsPrivateOnlyLobby => (BAUConfigs.PrivateOnlyLobby.Value || - (AmongUsClient.Instance != null && AmongUsClient.Instance.AmLocalHost)) && IsHost; - /// /// Gets whether the local player can move. /// diff --git a/src/Patches/Client/PrivateLobbyPatch.cs b/src/Patches/Client/PrivateLobbyPatch.cs deleted file mode 100644 index 5db0db7..0000000 --- a/src/Patches/Client/PrivateLobbyPatch.cs +++ /dev/null @@ -1,150 +0,0 @@ -using BetterAmongUs.Data.Config; -using BetterAmongUs.Helpers; -using BetterAmongUs.Modules; -using BetterAmongUs.Modules.Support; -using HarmonyLib; -using TMPro; -using UnityEngine; - -namespace BetterAmongUs.Patches.Client; - -[HarmonyPatch] -internal static class PrivateLobbyPatch -{ - private static GameObject? toggle; - private static readonly List? buttons = []; - private static TextMeshPro? toggleText; - - [HarmonyPatch(typeof(CreateGameOptions), nameof(CreateGameOptions.Show))] - [HarmonyPostfix] - private static void CreateGameOptions_Show_Postfix(CreateGameOptions __instance) - { - // Check if other mods have disabled private lobby feature - if (BAUModdedSupportFlags.HasFlag(BAUModdedSupportFlags.Disable_PrivateLobby)) - { - BAUConfigs.PrivateOnlyLobby.Value = false; - return; - } - - // Only create toggle UI once - if (toggle != null) - return; - - buttons.Clear(); - - // Clone April Fools toggle as template for private lobby toggle - toggle = UnityEngine.Object.Instantiate(__instance.AprilFoolsToggle, __instance.contentObjects.First().transform.parent); - if (toggle != null) - { - toggle.name = "PrivateOnlyLobby"; - - // Get the ON and OFF buttons from the toggle - buttons.Add(toggle.transform.Find("AprilOn").GetComponent()); - buttons.Add(toggle.transform.Find("AprilOff").GetComponent()); - if (buttons.Count < 2) - return; - - toggle.gameObject.SetActive(true); - - // Set up ON button click handler - var onButton = buttons[0]; - if (onButton != null) - { - onButton.gameObject.SetActive(false); - onButton.OnClick = new(); - onButton.OnClick.AddListener(() => TogglePrivateOnlyLobby(true)); - } - - // Set up OFF button click handler - var offButton = buttons[1]; - if (offButton != null) - { - offButton.gameObject.SetActive(false); - offButton.OnClick = new(); - offButton.OnClick.AddListener(() => TogglePrivateOnlyLobby(false)); - } - - // Position toggle at top-right of create game screen - var aspect = toggle.gameObject.AddComponent(); - aspect.Alignment = AspectPosition.EdgeAlignments.Top; - aspect.DistanceFromEdge = new Vector3(0.4f, 1.62f, 0); - aspect.AdjustPosition(); - - // Update toggle text label - var modeText = toggle.transform.Find("BlackSquare/ModeText"); - if (modeText != null) - { - var text = modeText.GetComponent(); - if (text != null) - { - text.DestroyTextTranslators(); - text.text = "Private Only Lobby"; - toggleText = text; - } - } - } - - // Initialize toggle state from saved configuration - TogglePrivateOnlyLobby(BAUConfigs.PrivateOnlyLobby.Value); - } - - private static void TogglePrivateOnlyLobby(bool modeOn) - { - if (buttons.Count < 2) - return; - - // Show both buttons first - buttons[0].gameObject.SetActive(true); - buttons[1].gameObject.SetActive(true); - - // Deselect both buttons - buttons[0].SelectButton(false); - buttons[1].SelectButton(false); - - // Select appropriate button based on mode - if (modeOn) - { - buttons[0].SelectButton(true); - } - else - { - buttons[1].SelectButton(true); - } - - // Save setting to configuration - BAUConfigs.PrivateOnlyLobby.Value = modeOn; - } - - [HarmonyPatch(typeof(LobbyInfoPane), nameof(LobbyInfoPane.Update))] - [HarmonyPostfix] - private static void LobbyInfoPane_Update_Postfix(LobbyInfoPane __instance) - { - // Enforce private lobby setting if enabled by host - if (BAUConfigs.PrivateOnlyLobby.Value && !GameState.IsLocalGame && GameState.IsHost) - { - // Ensure game stays private - if (AmongUsClient.Instance.IsGamePublic) - { - AmongUsClient.Instance.ChangeGamePublic(false); - } - - // Disable and visually indicate that public lobby button is locked - var button = __instance.HostPrivateButton.GetComponent(); - if (button != null) - { - button.enabled = false; - - // Change inactive sprite to cyan color to indicate locked state - var inactive = __instance.HostPrivateButton.transform.Find("Inactive"); - if (inactive != null) - { - var sprite = inactive.GetComponent(); - if (sprite != null) - { - sprite.color = new Color(0.35f, 1f, 1f, 1f); // Corrected colour assignment - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs b/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs index 73abbf3..b2d71ca 100644 --- a/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs +++ b/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs @@ -23,8 +23,11 @@ internal sealed class BetterGameSettings internal static OptionIntItem? KickLevelBelow; internal static OptionCheckboxItem? DetectCheatClients; internal static OptionCheckboxItem? DetectInvalidRPCs; + internal static OptionStringItem? RoleRandomizer; internal static OptionCheckboxItem? DesyncRoles; + internal static OptionCheckboxItem? DisableSabotagesForDead; + internal static OptionCheckboxItem? CancelInvalidSabotage; internal static OptionCheckboxItem? CensorDetectionReason; internal static OptionCheckboxItem? DisableSabotages; @@ -92,17 +95,19 @@ internal static class GameSettingsPatch // Gameplay Settings section { - // Show only for hosts in private lobbies - if (IsPreload || GameState.IsHost && GameState.IsPrivateOnlyLobby) + // Show only for hosts + if (IsPreload || GameState.IsHost) { // Normal game mode settings if (IsPreload || !GameState.IsHideNSeek) { OptionHeaderItem.Create(BetterSettingsTab, "BetterSetting.MainHeader.Gameplay"); BetterGameSettings.DisableSabotages = OptionCheckboxItem.Create(1500, BetterSettingsTab, "BetterSetting.Setting.DisableSabotages", false); + BetterGameSettings.DisableSabotagesForDead = OptionCheckboxItem.Create(1600, BetterSettingsTab, "BetterSetting.Setting.DisableSabotagesForDead", false); } + // Hide & Seek specific settings with dynamic impostor selection - else if (IsPreload || GameState.IsHideNSeek) + if (IsPreload || GameState.IsHideNSeek) { OptionHeaderItem.Create(BetterSettingsTab, "BetterSetting.MainHeader.HideNSeek"); OptionTitleItem.Create(BetterSettingsTab, $"{Translator.GetString(StringNames.ImpostorsCategory)}"); diff --git a/src/Resources/Lang/en_US.json b/src/Resources/Lang/en_US.json index fc9e3bf..0302e5f 100644 --- a/src/Resources/Lang/en_US.json +++ b/src/Resources/Lang/en_US.json @@ -95,6 +95,7 @@ "BetterSetting.Setting.DetectInvalidRPCs": "Detect invalid RPCs", "BetterSetting.Setting.RoleRandomizer": "Randomizer", "BetterSetting.Setting.DesyncRoles": "Desync roles to other clients", + "BetterSetting.Setting.DisableSabotagesForDead": "Disable sabotages for dead", "BetterSetting.Setting.DisableSabotages": "Disable sabotages", "BetterSetting.Setting.RemovePetOnDeath": "Remove pet on death", "BetterSetting.Setting.HideAndSeekImpNum": "# Seekers",