diff --git a/README.md b/README.md index 652339d..ab385a7 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,10 @@ A client-sided mod that enhances the experience for the popular game Among Us! ## Installation -Here's the updated guide with clear instructions for downloading the correct zip file and extracting it, as well as replacing the DLL if you've previously installed the mod, as this has been requested to be updated multiple times... - ### For First-Time Installation: 1. **Download the Correct Version**: - - Go to the [Releases](https://github.com/EnhancedNetwork/BetterAmongUs/releases) page + - Go to the [Releases](https://github.com/D1GQ/BetterAmongUs/releases) page - **Choose the correct zip file** for your platform: - Steam and itch.io users: `BAU-SteamItchio-Version.zip ` - Epic Games and Microsoft Store users: `BAU-EpicMsStore-Version.zip` @@ -39,7 +37,7 @@ Here's the updated guide with clear instructions for downloading the correct zip ### For Updating an Existing Installation: 1. **Download the DLL File**: - - Go to the [Releases](https://github.com/EnhancedNetwork/BetterAmongUs/releases) page + - Go to the [Releases](https://github.com/D1GQ/BetterAmongUs/releases) page - Download just the `BetterAmongUs.dll` file 2. **Replace the Old DLL**: @@ -51,22 +49,31 @@ Here's the updated guide with clear instructions for downloading the correct zip - Launch Among Us - Check that the mod version has been updated in the main menu -## Getting Started +### Linux Setup (Steam Only) -1. **Explore the Features**: Use the pause menu to discover new options and settings. -2. **Check the Commands**: Type `/commands` in the chat to view a list of all available commands. -3. **Stay Updated**: Keep an eye on this page for future updates and new features. +Use Proton (9.0+) with these launch options: +``` +WINEDLLOVERRIDES="winhttp=n,b" PROTON_NO_ESYNC=1 %command% +``` + +**Setup Steps:** +1. Enable Proton in Steam → Properties → Compatibility → Force Proton 9.0+ +2. Copy all mod files into your Among Us directory +3. Add the launch options above +4. Install Protontricks and set `winhttp` as a library override in winecfg ## Supported Platforms - ✅ Steam +- ✅ Linux + Steam - ✅ Epic Games - ✅ Microsoft Store - ✅ itch.io -- ⚠️ Android (Unknown) +- ✅ Android - ❌ iOS - ❌ Xbox/Playstation/Switch ## Supported Game Versions +- ✅ AU **v17.2.0** / **v2026.3.17**: (BAU v1.3.3) > - ✅ AU **v17.1.0** / **v2025.11.18**: (BAU v1.3.1) > - ✅ AU **v17.0.1** / **v2025.10.14**: (BAU v1.3.0) > - ✅ AU **v16.1.0** / **v2025.6.10**: (BAU v1.2.0 Beta 1) > @@ -123,6 +130,7 @@ BetterAmongUs offers a variety of commands to enhance your control over the game A huge thank you to everyone who contributed to making BetterAmongUs a reality! - **Head Developer**: [D1GQ](https://github.com/D1GQ) +- **Linux Support**: [Nyx](https://github.com/DeveloperNyx) ## Contacts betterauofficial@gmail.com @@ -130,7 +138,3 @@ betterauofficial@gmail.com ## Disclaimer **BetterAmongUs** is an unofficial, fan-made mod for **Among Us**. It is not affiliated with, endorsed by, or associated with **InnerSloth LLC** or the official **Among Us** game. All trademarks and copyrights related to **Among Us** are the property of **InnerSloth LLC**. This mod is created purely for entertainment purposes and to enhance the gaming experience. Use of this mod is at your own risk. - ---- - -**Important Note**: Always ensure your game version matches the supported versions listed above. If you encounter issues, verify you downloaded the correct zip file for your platform or replaced the DLL in the correct location. diff --git a/src/Managers/KickCooldownManager.cs b/src/Managers/KickCooldownManager.cs new file mode 100644 index 0000000..5092a35 --- /dev/null +++ b/src/Managers/KickCooldownManager.cs @@ -0,0 +1,36 @@ +using System.Collections; +using BepInEx.Unity.IL2CPP.Utils; +using BetterAmongUs.Patches.Gameplay.UI.Settings; +using UnityEngine; + +namespace BetterAmongUs.Managers; + +/// +/// Manages the cooldown period between player kick actions. +/// This cooldown is necessary to avoid bans by Innersloth anti-cheat. +/// +internal static class KickCooldownManager +{ + private static float CooldownSeconds => BetterGameSettings.KickCooldown.GetFloat(); + + private static float _lastTriggerTime; + + internal static bool IsReady() => Time.time - _lastTriggerTime > CooldownSeconds; + internal static void Trigger() => _lastTriggerTime = Time.time; + private static float TimeToNextAvailableTrigger() => CooldownSeconds - (Time.time - _lastTriggerTime); + + internal static void ScheduleAction(Action action) + { + AmongUsClient.Instance.StartCoroutine(RunScheduledAction(action)); + } + + private static IEnumerator RunScheduledAction(Action action) + { + while (!IsReady()) + { + yield return new WaitForSeconds(TimeToNextAvailableTrigger()); + } + Trigger(); + action(); + } +} \ No newline at end of file diff --git a/src/Modules/AntiCheat/BetterAntiCheat.cs b/src/Modules/AntiCheat/BetterAntiCheat.cs index 92bb9cd..374619e 100644 --- a/src/Modules/AntiCheat/BetterAntiCheat.cs +++ b/src/Modules/AntiCheat/BetterAntiCheat.cs @@ -51,31 +51,31 @@ internal static class BetterAntiCheat { string reason = TranslationStrings.AntiCheat_Reason_SickoMenuUser.LocalizedString; string kickMessage = TranslationStrings.AntiCheat_KickMessage.Format(TranslationStrings.AntiCheat_ByAntiCheat, reason); - player.Kick(true, kickMessage, true); + player.TryKick(true, kickMessage, true); } else if (BetterDataManager.Files.BetterDataFile.AUMData.Any(info => info.CheckPlayerData(player.Data))) { string reason = TranslationStrings.AntiCheat_Reason_AUMUser.LocalizedString; string kickMessage = TranslationStrings.AntiCheat_KickMessage.Format(TranslationStrings.AntiCheat_ByAntiCheat, reason); - player.Kick(true, kickMessage, true); + player.TryKick(true, kickMessage, true); } else if (BetterDataManager.Files.BetterDataFile.KNData.Any(info => info.CheckPlayerData(player.Data))) { string reason = TranslationStrings.AntiCheat_Reason_KNUser.LocalizedString; string kickMessage = TranslationStrings.AntiCheat_KickMessage.Format(TranslationStrings.AntiCheat_ByAntiCheat, reason); - player.Kick(true, kickMessage, true); + player.TryKick(true, kickMessage, true); } else if (BetterDataManager.Files.BetterDataFile.MMCData.Any(info => info.CheckPlayerData(player.Data))) { string reason = TranslationStrings.AntiCheat_Reason_MMCUser.LocalizedString; string kickMessage = TranslationStrings.AntiCheat_KickMessage.Format(TranslationStrings.AntiCheat_ByAntiCheat, reason); - player.Kick(true, kickMessage, true); + player.TryKick(true, kickMessage, true); } else if (BetterDataManager.Files.BetterDataFile.CheatData.Any(info => info.CheckPlayerData(player.Data))) { string reason = TranslationStrings.AntiCheat_Reason_KnownCheater.LocalizedString; string kickMessage = TranslationStrings.AntiCheat_KickMessage.Format(TranslationStrings.AntiCheat_ByAntiCheat, reason); - player.Kick(true, kickMessage, true); + player.TryKick(true, kickMessage, true); } } } diff --git a/src/MonoScripts/PlayerInfoDisplay.cs b/src/MonoScripts/PlayerInfoDisplay.cs index 4432f7d..9c33aac 100644 --- a/src/MonoScripts/PlayerInfoDisplay.cs +++ b/src/MonoScripts/PlayerInfoDisplay.cs @@ -277,7 +277,7 @@ internal class PlayerInfoDisplay : MonoBehaviour if (GameState.IsHost && BetterGameSettings.InvalidFriendCode.GetBool()) { string kickMessage = TranslationStrings.AntiCheat_KickMessage.Format(TranslationStrings.AntiCheat_ByAntiCheat, TranslationStrings.AntiCheat_Reason_InvalidFriendCode); - _player.Kick(true, kickMessage, true); + _player.TryKick(true, kickMessage, true); } } diff --git a/src/Patches/Gameplay/Anticheat/CheckPlayerLevelPatch.cs b/src/Patches/Gameplay/Anticheat/CheckPlayerLevelPatch.cs index 42d55a5..e18f9d2 100644 --- a/src/Patches/Gameplay/Anticheat/CheckPlayerLevelPatch.cs +++ b/src/Patches/Gameplay/Anticheat/CheckPlayerLevelPatch.cs @@ -1,6 +1,6 @@ -using BetterAmongUs.Modules; +using BetterAmongUs.Utilities; +using BetterAmongUs.Modules; using BetterAmongUs.Patches.Gameplay.UI.Settings; -using BetterAmongUs.Utilities; using HarmonyLib; namespace BetterAmongUs.Patches.Gameplay.Anticheat; @@ -20,7 +20,7 @@ internal static class CheckPlayerLevelPatch // Kick players below minimum level if (!__instance.IsLocalPlayer() && BetterGameSettings.KickLevel.GetBool() && __instance.Data.PlayerLevel < BetterGameSettings.KickLevelBelow.GetInt()) { - __instance.Kick(setReasonInfo: $" is level {__instance.Data.PlayerLevel}, level must be equal or above {BetterGameSettings.KickLevelBelow.GetInt()} to join"); + __instance.TryKick(setReasonInfo: $" is level {__instance.Data.PlayerLevel}, level must be equal or above {BetterGameSettings.KickLevelBelow.GetInt()} to join"); } } } diff --git a/src/Patches/Gameplay/Player/PlayerJoinAndLeftPatch.cs b/src/Patches/Gameplay/Player/PlayerJoinAndLeftPatch.cs index 99b5da5..e3bf117 100644 --- a/src/Patches/Gameplay/Player/PlayerJoinAndLeftPatch.cs +++ b/src/Patches/Gameplay/Player/PlayerJoinAndLeftPatch.cs @@ -1,7 +1,9 @@ +using System.Collections; using BepInEx.Unity.IL2CPP.Utils; using BetterAmongUs.Data; using BetterAmongUs.Data.Config; using BetterAmongUs.Generated; +using BetterAmongUs.Managers; using BetterAmongUs.Modules; using BetterAmongUs.Modules.Support; using BetterAmongUs.MonoScripts.Extended; @@ -10,7 +12,6 @@ using BetterAmongUs.Patches.Gameplay.UI.Settings; using BetterAmongUs.Utilities; using HarmonyLib; using InnerNet; -using System.Collections; namespace BetterAmongUs.Patches.Gameplay.Player; @@ -38,6 +39,7 @@ internal static class PlayerJoinAndLeftPatch { if (GameState.IsHost) { + KickCooldownManager.Trigger(); __instance.StartCoroutine(CoCheckPlayerOnJoin(data)); } } @@ -59,8 +61,7 @@ internal static class PlayerJoinAndLeftPatch if (player != null) { if (TextFileHandler.CompareStringMatch(BetterDataManager.Files.banPlayerListFilePath, - BAUPlugin.AllPlayerControls.Select(player => player.Data.FriendCode) - .Concat(BAUPlugin.AllPlayerControls.Select(player => player.GetHashPuid())).ToArray())) + [player.Data.FriendCode, player.GetHashPuid()])) { player.Kick(true, TranslationStrings.AntiCheat_BanPlayerListMessage.LocalizedString, bypassDataCheck: true); yield break; @@ -75,7 +76,7 @@ internal static class PlayerJoinAndLeftPatch { if (TextFileHandler.CompareStringFilters(BetterDataManager.Files.banNameListFilePath, [player.Data.PlayerName])) { - player?.Kick(true, TranslationStrings.AntiCheat_BanPlayerListMessage.LocalizedString, bypassDataCheck: true); + player.Kick(true, TranslationStrings.AntiCheat_BanNameListMessage.LocalizedString, bypassDataCheck: true); } } } @@ -86,6 +87,8 @@ internal static class PlayerJoinAndLeftPatch [HarmonyPostfix] private static void AmongUsClient_OnPlayerLeft_Postfix(ClientData data, DisconnectReasons reason) { + KickCooldownManager.Trigger(); + // Reclaim favorite color when player leaves in lobby if (GameState.IsLobby) { diff --git a/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs b/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs index 05417a2..ab230c1 100644 --- a/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs +++ b/src/Patches/Gameplay/UI/Settings/GameSettingsPatch.cs @@ -11,6 +11,7 @@ namespace BetterAmongUs.Patches.Gameplay.UI.Settings; // Custom game settings definitions internal sealed class BetterGameSettings { + internal static OptionFloatItem? KickCooldown; internal static OptionStringItem? WhenCheating; internal static OptionCheckboxItem? InvalidFriendCode; internal static OptionCheckboxItem? UseBanPlayerList; @@ -67,6 +68,7 @@ internal static class GameSettingsPatch if (IsPreload || GameState.IsHost) { OptionTitleItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_TextHeader_HostOnly); + BetterGameSettings.KickCooldown = OptionFloatItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_KickCooldown, (0f, 3f, 0.1f), 2f, ("", "s")); BetterGameSettings.WhenCheating = OptionStringItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_WhenCheating, [TranslationStrings.BetterSetting_Setting_WhenCheating_Notify, TranslationStrings.BetterSetting_Setting_WhenCheating_Kick, TranslationStrings.BetterSetting_Setting_WhenCheating_Ban], 2); BetterGameSettings.InvalidFriendCode = OptionCheckboxItem.Create(BetterSettingsTab, TranslationStrings.BetterSetting_Setting_InvalidFriendCode, true); diff --git a/src/Resources/Lang/en_US.json b/src/Resources/Lang/en_US.json index 3042bc2..ec8ec52 100644 --- a/src/Resources/Lang/en_US.json +++ b/src/Resources/Lang/en_US.json @@ -79,6 +79,7 @@ "BetterSetting.TextHeader.HostOnly": "Host Only", "BetterSetting.TextHeader.Detections": "Detections", + "BetterSetting.Setting.KickCooldown": "Kick cooldown", "BetterSetting.Setting.WhenCheating": "When a player is caught cheating", "BetterSetting.Setting.WhenCheating.Notify": "Notify", "BetterSetting.Setting.WhenCheating.Kick": "Kick", diff --git a/src/Utilities/PlayerControlUtils.cs b/src/Utilities/PlayerControlUtils.cs index 0fc610c..128d3b9 100644 --- a/src/Utilities/PlayerControlUtils.cs +++ b/src/Utilities/PlayerControlUtils.cs @@ -1,6 +1,7 @@ using AmongUs.GameOptions; using BetterAmongUs.Data; using BetterAmongUs.Generated; +using BetterAmongUs.Managers; using BetterAmongUs.Modules; using BetterAmongUs.MonoScripts.Extended; using BetterAmongUs.Patches.Gameplay.Player; @@ -127,42 +128,113 @@ internal static class PlayerControlUtils return true; } + /// + /// Kicks a player from the game as soon as it is safe to do so. + /// + /// The player to kick. + /// Whether to ban the player. + /// Custom reason message for the kick. + /// Whether this is an anti-cheat related kick. + /// Whether to bypass the data collection check. + /// Whether to force a ban regardless of settings. + internal static void Kick(this PlayerControl player, bool ban = false, string setReasonInfo = "", bool antiCheatBan = false, bool bypassDataCheck = false, bool forceBan = false) + { + if (!player.CanKick(ban, antiCheatBan, bypassDataCheck, forceBan, out var shouldBan)) return; + KickCooldownManager.ScheduleAction(() => { player.PerformKick(shouldBan, setReasonInfo, antiCheatBan); }); + } + + /// + /// Kicks a player from the game immediately if it is safe to do so, otherwise does nothing. + /// This should be used instead of in places where this method is repeatedly called, like Update methods for example. + /// + /// The player to kick. + /// Whether to ban the player. + /// Custom reason message for the kick. + /// Whether this is an anti-cheat related kick. + /// Whether to bypass the data collection check. + /// Whether to force a ban regardless of settings. + internal static void TryKick(this PlayerControl player, bool ban = false, string setReasonInfo = "", bool antiCheatBan = false, bool bypassDataCheck = false, bool forceBan = false) + { + if (!KickCooldownManager.IsReady()) return; + if (!player.CanKick(ban, antiCheatBan, bypassDataCheck, forceBan, out var shouldBan)) return; + KickCooldownManager.Trigger(); + player.PerformKick(shouldBan, setReasonInfo, antiCheatBan); + } + + /// + /// Kicks a player from the game immediately, regardless of cooldown. + /// Only use this if the target player is extremely dangerous and must be removed immediately. + /// + /// The player to kick. + /// Whether to ban the player. + /// Custom reason message for the kick. + /// Whether this is an anti-cheat related kick. + /// Whether to bypass the data collection check. + /// Whether to force a ban regardless of settings. + internal static void KickImmediate(this PlayerControl player, bool ban = false, string setReasonInfo = "", bool antiCheatBan = false, bool bypassDataCheck = false, bool forceBan = false) + { + if (!player.CanKick(ban, antiCheatBan, bypassDataCheck, forceBan, out var shouldBan)) return; + player.PerformKick(shouldBan, setReasonInfo, antiCheatBan); + } + + /// + /// Determines whether the specified player can be kicked from the game and sets the outcome. + /// + /// The player to evaluate. + /// Whether to ban the player. + /// Whether this is an anti-cheat related kick. + /// Whether to bypass the data collection check. + /// Whether to force a ban regardless of settings. + /// + /// When the method returns, this value indicates whether the player should be banned + /// based on the provided flags and internal checks. + /// + /// true if the player can be kicked/banned, false otherwise. + private static bool CanKick(this PlayerControl player, bool ban, bool antiCheatBan, bool bypassDataCheck, + bool forceBan, out bool shouldBan) + { + shouldBan = ban || forceBan; + + if (!GameState.IsHost || player.IsLocalPlayer() || (!player.DataIsCollected() && !bypassDataCheck) || player.IsHost() || player.isDummy) + return false; + + if (forceBan || !antiCheatBan) return true; + + return HandleAntiCheatBanCheck(shouldBan, out shouldBan); + } + + /// + /// Determines whether a player should be banned when an anti‑cheat check is performed. + /// + /// Whether the player has already been flagged for banning before this method is called. + /// The output value that indicates whether the player should ultimately be banned. + /// + /// true if the player can be kicked or banned.
+ /// false if is set to 0 (Notify). + ///
+ private static bool HandleAntiCheatBanCheck(bool ban, out bool shouldBan) + { + shouldBan = ban && BetterGameSettings.WhenCheating.GetStringIndex() == 2; + return BetterGameSettings.WhenCheating.GetStringIndex() != 0; + } + /// /// Kicks a player from the game with optional ban and anti-cheat features. /// /// The player to kick. /// Whether to ban the player. /// Custom reason message for the kick. - /// Whether this is an anti-cheat related kick. - /// Whether to bypass the data collection check. - /// Whether to force a ban regardless of settings. - internal static void Kick(this PlayerControl player, bool ban = false, string setReasonInfo = "", bool AntiCheatBan = false, bool bypassDataCheck = false, bool forceBan = false) + /// Whether this is an anti-cheat related kick. + private static void PerformKick(this PlayerControl player, bool ban = false, string setReasonInfo = "", bool antiCheatBan = false) { - var Ban = ban || forceBan; - - if (!GameState.IsHost || player.IsLocalPlayer() || (!player.DataIsCollected() && !bypassDataCheck) || player.IsHost() || player.isDummy) - { - return; - } - - if (AntiCheatBan) - { - if (BetterGameSettings.WhenCheating.GetStringIndex() == 0 && !forceBan) - { - return; - } - - Ban = (Ban && BetterGameSettings.WhenCheating.GetStringIndex() == 2) || forceBan; - } - if (setReasonInfo != "") { - PlayerJoinAndLeftPatch.BetterShowNotification(player.Data, forceReasonText: string.Format(setReasonInfo, Ban ? TranslationStrings.AntiCheat_Ban.LocalizedString.ToLower() : TranslationStrings.AntiCheat_Kick.LocalizedString.ToLower())); + PlayerJoinAndLeftPatch.BetterShowNotification(player.Data, forceReasonText: string.Format(setReasonInfo, ban ? TranslationStrings.AntiCheat_Ban.LocalizedString.ToLower() : TranslationStrings.AntiCheat_Kick.LocalizedString.ToLower())); } - AmongUsClient.Instance.KickPlayer(player.GetClientId(), Ban); + AmongUsClient.Instance.KickPlayer(player.GetClientId(), ban); - player.ExtendedData().AntiCheatInfo.BannedByAntiCheat = AntiCheatBan; + player.ExtendedData().AntiCheatInfo.BannedByAntiCheat = antiCheatBan; } /// diff --git a/src/build/Generated/TranslationStrings.cs b/src/build/Generated/TranslationStrings.cs index 9878aeb..2cba8ab 100644 --- a/src/build/Generated/TranslationStrings.cs +++ b/src/build/Generated/TranslationStrings.cs @@ -388,6 +388,11 @@ public static class TranslationStrings /// public static readonly TranslationString BetterSetting_TextHeader_Detections = new("BetterSetting.TextHeader.Detections"); + /// + /// Base Translation: Kick cooldown + /// + public static readonly TranslationString BetterSetting_Setting_KickCooldown = new("BetterSetting.Setting.KickCooldown"); + /// /// Base Translation: When a player is caught cheating ///