diff --git a/BetterAmongUs.csproj b/BetterAmongUs.csproj index 612220b..1d0bb6b 100644 --- a/BetterAmongUs.csproj +++ b/BetterAmongUs.csproj @@ -25,7 +25,4 @@ - - - \ No newline at end of file diff --git a/Modules/AntiCheat.cs b/Modules/AntiCheat.cs index e98bc24..1e3a563 100644 --- a/Modules/AntiCheat.cs +++ b/Modules/AntiCheat.cs @@ -3,6 +3,7 @@ using HarmonyLib; using Hazel; using InnerNet; using UnityEngine; +using UnityEngine.Purchasing; namespace BetterAmongUs; @@ -194,7 +195,7 @@ class AntiCheat if (player.IsAlive() && GameStates.IsInGamePlay && !GameStates.IsMeeting && !GameStates.IsExilling) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {player.IsAlive()} - {GameStates.IsInGamePlay} - {!GameStates.IsMeeting} - {!GameStates.IsExilling}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {player.IsAlive()} && {GameStates.IsInGamePlay} && {!GameStates.IsMeeting} && {!GameStates.IsExilling}"); } return; @@ -205,7 +206,7 @@ class AntiCheat if ((!player.IsImpostorTeam() && Role != RoleTypes.Engineer) || player.Data.IsDead) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {player.IsImpostorTeam()} - {Role != RoleTypes.Engineer} - {player.Data.IsDead}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {player.IsImpostorTeam()} && {Role != RoleTypes.Engineer} || {player.Data.IsDead}"); } return; @@ -233,8 +234,8 @@ class AntiCheat if (!player.IsImpostorTeam() || !player.IsAlive() || player.IsInVanish() || target.IsImpostorTeam()) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {!player.IsImpostorTeam()} - {player.IsInVanish()}" + - $" - {!target.IsAlive()} - {target.IsImpostorTeam()}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {!player.IsImpostorTeam()} || {player.IsInVanish()}" + + $" || {!target.IsAlive()} || {target.IsImpostorTeam()}"); } } } @@ -266,36 +267,31 @@ class AntiCheat BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)} - {Enum.GetName((SystemTypes)callId)}"); Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {!player.IsImpostorTeam()}"); - return; - } - if (callId is (byte)RpcCalls.UpdateSystem) - { - byte Type = reader.ReadByte(); - if (Type is (byte)SystemTypes.Reactor - or (byte)SystemTypes.Laboratory - or (byte)SystemTypes.Comms - or (byte)SystemTypes.LifeSupp - or (byte)SystemTypes.MushroomMixupSabotage - or (byte)SystemTypes.HeliSabotage) - { - BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)} - {Enum.GetName((SystemTypes)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {!player.IsImpostorTeam()}"); - } - return; } } } - if (player.IsImpostorTeam()) + if (callId is (byte)RpcCalls.CompleteTask) { - if (callId is (byte)RpcCalls.CompleteTask or (byte)RpcCalls.BootFromVent) + var taskId = reader.ReadPackedUInt32(); + + if (player.IsImpostorTeam() || !player.Data.Tasks.ToArray().Any(task => task.Id == taskId) + || player.BetterData().LastTaskId == taskId || player.BetterData().LastTaskId != taskId + && player.BetterData().TimeSinceLastTask < 1.25f) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {player.IsImpostorTeam()}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {player.IsImpostorTeam()} || {!player.Data.Tasks.ToArray().Any(task => task.Id == taskId)} ||" + + $" {player.BetterData().LastTaskId == taskId} || {player.BetterData().LastTaskId != taskId} && {player.BetterData().TimeSinceLastTask < 1.25f}"); + + player.BetterData().TimeSinceLastTask = 0f; + player.BetterData().LastTaskId = taskId; return; } + + player.BetterData().TimeSinceLastTask = 0f; + player.BetterData().LastTaskId = taskId; } if (callId is (byte)RpcCalls.Pet or (byte)RpcCalls.CancelPet) @@ -309,12 +305,37 @@ class AntiCheat return; } } - catch (Exception ex) + catch (Exception ex) { Logger.Error(ex.ToString()); } } + // Check game states when sabotaging + public static bool RpcUpdateSystemCheck(PlayerControl player, SystemTypes systemType, byte amount) + { + if (!player.IsImpostorTeam() || !GameStates.IsSystemActive(systemType) && GameStates.IsAnySabotageActive() || Utils.SystemTypeIsSabotage(systemType) + || ShipStatus.Instance.AllDoors.ToArray().Any(door => !door.IsOpen)) + { + BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName(systemType)}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName(systemType)} Start Sabotage: {!player.IsImpostorTeam()} || {!GameStates.IsSystemActive(systemType)}" + + $"&& {GameStates.IsAnySabotageActive()} || {Utils.SystemTypeIsSabotage(systemType)} || {ShipStatus.Instance.AllDoors.ToArray().Any(door => !door.IsOpen)}"); + return false; + } + + // Fix Sabo + if (amount == 16) + { + if (!GameStates.IsSystemActive(systemType)) + { + BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName(systemType)}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName(systemType)} Fix Sabotage: {!GameStates.IsSystemActive(systemType)}"); + } + } + + return true; + } + // Check notify and cancel out request for invalid rpcs public static bool CheckCancelRPC(PlayerControl player, byte callId, MessageReader Oldreader) { @@ -361,8 +382,8 @@ class AntiCheat || player.inMovingPlat || player.onLadder || player.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {GameStates.IsMeeting} - {GameStates.IsHideNSeek} - {!player.IsAlive()} - {player.IsInVent()} - {player.shapeshifting}" + - $" - {player.inMovingPlat} - {player.onLadder} - {player.MyPhysics.Animations.IsPlayingAnyLadderAnimation()}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {GameStates.IsMeeting} && {GameStates.IsHideNSeek} || {!player.IsAlive()} || {player.IsInVent()} || {player.shapeshifting}" + + $" || {player.inMovingPlat} || {player.onLadder} || {player.MyPhysics.Animations.IsPlayingAnyLadderAnimation()}"); if (GameStates.IsHost) { @@ -382,7 +403,7 @@ class AntiCheat if (!UnityEngine.Object.FindAnyObjectByType() || !deadPlayerInfo.IsDead || deadPlayerInfo == player.Data) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {!UnityEngine.Object.FindAnyObjectByType()} - {!deadPlayerInfo.IsDead} - {deadPlayerInfo == player.Data}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {!UnityEngine.Object.FindAnyObjectByType()} || {!deadPlayerInfo.IsDead} || {deadPlayerInfo == player.Data}"); if (GameStates.IsHost) { return false; @@ -421,14 +442,14 @@ class AntiCheat if (Role is not RoleTypes.Shapeshifter || !player.IsAlive()) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)} 1: {Role is not RoleTypes.Shapeshifter} - {!player.IsAlive()}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)} 1: {Role is not RoleTypes.Shapeshifter} || {!player.IsAlive()}"); return false; } else if (!flag && !GameStates.IsMeeting && !GameStates.IsExilling && !player.IsInVent()) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)} 2: {!flag} - {!GameStates.IsMeeting} - {!GameStates.IsExilling} - {!player.IsInVent()}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)} 2: {!flag} && {!GameStates.IsMeeting} && {!GameStates.IsExilling} && {!player.IsInVent()}"); return false; } } @@ -438,7 +459,7 @@ class AntiCheat if (Role is not RoleTypes.Phantom || player.Data.IsDead) { BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)} 1: {Role is not RoleTypes.Phantom} - {player.Data.IsDead}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)} 1: {Role is not RoleTypes.Phantom} || {player.Data.IsDead}"); return false; } @@ -466,10 +487,16 @@ class AntiCheat if (player.DataIsCollected() == true && !GameStates.IsLocalGame && GameStates.IsVanillaServer) { - if (callId is (byte)RpcCalls.SetName or (byte)RpcCalls.CheckName or (byte)RpcCalls.SetLevel) + if (callId is (byte)RpcCalls.CheckName or (byte)RpcCalls.SetLevel) { BetterNotificationManager.NotifyCheat(player, $"Invalid Set RPC: {Enum.GetName((RpcCalls)callId)}"); Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {player.DataIsCollected() == true}"); + + if (callId is (byte)RpcCalls.CheckName) + { + var name = reader.ReadString(); + Logger.LogCheat($"{player.Data.PlayerName} Has tried to change their name to '{name}' but has been undone!"); + } return false; } } @@ -519,7 +546,7 @@ class AntiCheat or (byte)RpcCalls.CheckVanish) { BetterNotificationManager.NotifyCheat(player, $"Invalid Lobby RPC: {Enum.GetName((RpcCalls)callId)}"); - Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {GameStates.IsInGame} - {GameStates.IsLobby}"); + Logger.LogCheat($"{player.Data.PlayerName} {Enum.GetName((RpcCalls)callId)}: {GameStates.IsInGame} && {GameStates.IsLobby}"); return false; } } diff --git a/Modules/BetterDataManager.cs b/Modules/BetterDataManager.cs index 7d65d5f..6068fe7 100644 --- a/Modules/BetterDataManager.cs +++ b/Modules/BetterDataManager.cs @@ -1,10 +1,15 @@ -using System.Collections.Generic; -using System.Text.Json; +using System.Text.Json; namespace BetterAmongUs; class BetterDataManager { + private static string filePath = GetFilePath("BetterData"); + public static string filePathFolder = Path.Combine(Main.GetGamePathToAmongUs(), $"Better_Data"); + public static string filePathFolderSaveInfo = Path.Combine(filePathFolder, $"SaveInfo"); + public static string banPlayerListFile = Path.Combine(filePathFolderSaveInfo, "BanPlayerList.txt"); + public static string banNameListFile = Path.Combine(filePathFolderSaveInfo, "BanNameList.txt"); + public static string GetFilePath(string name) { return Path.Combine(Main.GetDataPathToAmongUs(), $"{name}.json"); @@ -12,7 +17,25 @@ class BetterDataManager public static void SetUp() { - string filePath = GetFilePath("BetterData"); + if (!Directory.Exists(filePathFolder)) + { + Directory.CreateDirectory(filePathFolder); + } + + if (!Directory.Exists(filePathFolderSaveInfo)) + { + Directory.CreateDirectory(filePathFolderSaveInfo); + } + + if (!File.Exists(banPlayerListFile)) + { + File.WriteAllText(banPlayerListFile, "// Example\nFriendCode#0000\nHashPUID\n// Or\nFriendCode#0000, HashPUID"); + } + + if (!File.Exists(banNameListFile)) + { + File.WriteAllText(banNameListFile, "// Example\nBanName1\nBanName2"); + } if (!File.Exists(filePath)) { @@ -90,7 +113,7 @@ class BetterDataManager return jsonData[category][name]; } } - catch (Exception ex) + catch (Exception ex) { Logger.Error(ex.ToString()); } @@ -98,6 +121,37 @@ class BetterDataManager return string.Empty; } + public static void SaveBanList(string friendCode = "", string hashPUID = "") + { + if (!string.IsNullOrEmpty(friendCode) || !string.IsNullOrEmpty(hashPUID)) + { + // Create the new string with the separator if both are not empty + string newText = string.Empty; + + if (!string.IsNullOrEmpty(friendCode)) + { + newText = friendCode; + } + + if (!string.IsNullOrEmpty(hashPUID)) + { + if (!string.IsNullOrEmpty(newText)) + { + newText += ", "; + } + newText += Utils.GetHashPuid(hashPUID); + } + + // Check if the file already contains the new entry + if (!File.Exists(banPlayerListFile) || !File.ReadLines(banPlayerListFile).Any(line => line.Equals(newText))) + { + // Append the new string to the file if it's not already present + File.AppendAllText(banPlayerListFile, Environment.NewLine + newText); + } + } + } + + public static void SaveCheatData(string puid, string friendCode, string name, string category = "cheatData", string reason = "None") { string filePath = GetFilePath("BetterData"); @@ -251,6 +305,11 @@ class BetterDataManager Logger.LogCheat("Cleared cheat memory and data"); } + public static void LoadData() + { + LoadCheatData(); + } + public static void LoadCheatData() { try diff --git a/Modules/BetterHostManager.cs b/Modules/BetterHostManager.cs index 6d78d84..5e66038 100644 --- a/Modules/BetterHostManager.cs +++ b/Modules/BetterHostManager.cs @@ -1,8 +1,8 @@ using AmongUs.GameOptions; using Hazel; using InnerNet; -using UnityEngine; using System.Text; +using UnityEngine; namespace BetterAmongUs; @@ -167,6 +167,17 @@ class BetterHostManager } break; + case (byte)RpcCalls.CompleteTask: + { + _ = new LateTask(() => + { + RPC.SyncAllNames(force: true); + }, 1f, shoudLog: false); + + shouldReturn = true; + } + break; + default: shouldReturn = true; break; @@ -220,6 +231,10 @@ class BetterHostManager else if (GameStates.IsInGamePlay) { string Role = $"{player.GetRoleName()}"; + if (!player.IsImpostorTeam() && player.myTasks.Count > 0) + { + Role += $" ({player.Data.Tasks.ToArray().Where(task => task.Complete).Count()}/{player.Data.Tasks.Count})"; + } if (player.IsImpostorTeam() is false || target.IsImpostorTeam() is false) { if (target.IsAlive() && player != target) diff --git a/Modules/DebugMenu.cs b/Modules/DebugMenu.cs index 547edb7..6af6a2a 100644 --- a/Modules/DebugMenu.cs +++ b/Modules/DebugMenu.cs @@ -53,6 +53,11 @@ public class DebugMenu : MonoBehaviour BetterDataManager.ClearCheatData(); }); + AddButtonToTab(Main, "Send Better RPC", () => + { + RPC.SendBetterCheck(); + }, true); + AddButtonToTab(HostOnly, "Sync All Names", () => { RPC.SyncAllNames(); diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs index 7cd6573..985e79f 100644 --- a/Modules/Debugger.cs +++ b/Modules/Debugger.cs @@ -6,7 +6,7 @@ class Logger public static void Log(string info, string tag = "Log") { string mark = $"{DateTime.Now:HH:mm} [BetterLog][{tag}]"; - string logFilePath = Path.Combine(Environment.CurrentDirectory, "better-log.txt"); + string logFilePath = Path.Combine(BetterDataManager.filePathFolder, "better-log.txt"); string newLine = $"{mark}: {info}"; File.AppendAllText(logFilePath, newLine + Environment.NewLine); Main.Logger.LogInfo($"[{tag}] {info}"); @@ -34,7 +34,7 @@ class Logger } string mark = $"{DateTime.Now:HH:mm} [BetterLog][PrivateLog][{tag}]"; - string logFilePath = Path.Combine(Environment.CurrentDirectory, "better-log.txt"); + string logFilePath = Path.Combine(BetterDataManager.filePathFolder, "better-log.txt"); string newLine = $"{mark}: " + Encryptor.Encrypt($"{info}"); File.AppendAllText(logFilePath, newLine + Environment.NewLine); } diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 78de8c0..168b03d 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using Hazel; using InnerNet; using TMPro; using UnityEngine; @@ -103,7 +102,7 @@ static class ExtendedPlayerControl // Kick player public static void Kick(this PlayerControl player, bool ban = false, string setReasonInfo = "") { - if (!GameStates.IsHost || !player.DataIsCollected() || player.IsHost() || player.BetterData().BannedByAntiCheat) + if (!GameStates.IsHost || PlayerControl.LocalPlayer == player || !player.DataIsCollected() || player.IsHost() || player.BetterData().BannedByAntiCheat || player.isDummy) { return; } @@ -164,7 +163,7 @@ static class ExtendedPlayerControl // Check if player role name public static string GetRoleName(this PlayerControl player) { - if (!player.IsAlive() && !player.IsGhostRole() && Main.GetRoleName.TryGetValue((int)player.BetterData().DeadDisplayRole, out var roleName)) + if (!player.IsAlive() && !player.IsGhostRole() && Main.GetRoleName.TryGetValue((int)player.BetterData().RoleInfo.DeadDisplayRole, out var roleName)) { return roleName; } diff --git a/Modules/ExtendedPlayerInfo.cs b/Modules/ExtendedPlayerInfo.cs index 425bdfe..edf7f3d 100644 --- a/Modules/ExtendedPlayerInfo.cs +++ b/Modules/ExtendedPlayerInfo.cs @@ -10,35 +10,54 @@ public class ExtendedPlayerInfo public bool IsBetterHost { get; set; } = false; public bool IsTOHEHost { get; set; } = false; public bool BannedByAntiCheat { get; set; } = false; - public bool HasNoisemakerNotify { get; set; } = false; + + // Track Game Info + public float TimeSinceLastTask { get; set; } = 5f; + public uint LastTaskId { get; set; } = 0; public int TimesCalledMeeting { get; set; } = 0; + public DisconnectReasons DisconnectReason { get; set; } = DisconnectReasons.Unknown; + public ExtendedRoleInfo? RoleInfo { get; set; } +} + +public class ExtendedRoleInfo +{ + public int Kills { get; set; } = 0; + public bool HasNoisemakerNotify { get; set; } = false; public RoleTypes DeadDisplayRole { get; set; } } public static class PlayerControlExtensions { - private static readonly Dictionary playerInfo = []; + public static readonly Dictionary playerInfo = []; // Get BetterData from PlayerControl - public static ExtendedPlayerInfo BetterData(this PlayerControl player) + public static ExtendedPlayerInfo? BetterData(this PlayerControl player) { - if (!playerInfo.ContainsKey(player.Data)) + if (player == null) return null; + + if (!playerInfo.ContainsKey(player.Data.Puid)) { - playerInfo[player.Data] = new ExtendedPlayerInfo(); + playerInfo[player.Data.Puid] = new ExtendedPlayerInfo + { + RoleInfo = new ExtendedRoleInfo() + }; } - return playerInfo[player.Data]; + return playerInfo[player.Data.Puid]; } // Get BetterData from NetworkedPlayerInfo - public static ExtendedPlayerInfo BetterData(this NetworkedPlayerInfo info) + public static ExtendedPlayerInfo? BetterData(this NetworkedPlayerInfo info) { - if (!playerInfo.ContainsKey(info)) + if (!playerInfo.ContainsKey(info.Puid)) { - playerInfo[info] = new ExtendedPlayerInfo(); + playerInfo[info.Puid] = new ExtendedPlayerInfo + { + RoleInfo = new ExtendedRoleInfo() + }; } - return playerInfo[info]; + return playerInfo[info.Puid]; } // Get BetterData from ClientData @@ -48,12 +67,15 @@ public static class PlayerControlExtensions if (player != null) { - if (!playerInfo.ContainsKey(player.Data)) + if (!playerInfo.ContainsKey(player.Data.Puid)) { - playerInfo[player.Data] = new ExtendedPlayerInfo(); + playerInfo[player.Data.Puid] = new ExtendedPlayerInfo + { + RoleInfo = new ExtendedRoleInfo() + }; } - return playerInfo[player.Data]; + return playerInfo[player.Data.Puid]; } return null; diff --git a/Modules/FileChecker.cs b/Modules/FileChecker.cs index 147197a..550263a 100644 --- a/Modules/FileChecker.cs +++ b/Modules/FileChecker.cs @@ -7,7 +7,7 @@ class FileChecker { private static bool Enabled = true; private static readonly ReadOnlyCollection UnsupportedBepInExMods = new(new List { "TOHE", "YuAntiCheat" }); // Put BepInEx BepInPlugin name, not dll name here lol. - private static readonly ReadOnlyCollection BannedBepInExMods = new(new List { "MalumMenu", "MalumMenu-Yu", "MalumMenuYu", "AUnlocker" }); // Put BepInEx BepInPlugin name, not dll name here lol. + private static readonly ReadOnlyCollection BannedBepInExMods = new(new List { "MalumMenu", "MalumMenu-Yu", "MalumMenuYu"/*, "AUnlocker" */}); // Put BepInEx BepInPlugin name, not dll name here lol. private static readonly ReadOnlyCollection KeyWordsInVersionInfo = new(new List { "Malum", "Sicko", "AUM" }); // Banned words for version text public static string UnauthorizedReason = string.Empty; public static List CheatTags = []; // For API report @@ -232,7 +232,7 @@ class FileChecker { foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - if (assembly.FullName.Contains(modName)) + if (assembly.FullName.Contains(modName, StringComparison.OrdinalIgnoreCase)) { return true; } diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 6baa41c..068c4f8 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -1,5 +1,4 @@ using AmongUs.GameOptions; -using BetterAmongUs.Patches; namespace BetterAmongUs; @@ -18,6 +17,67 @@ public static class GameStates public static bool DleksIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Dleks; public static bool AirshipIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Airship; public static bool FungleIsActive => (MapNames)GameOptionsManager.Instance.CurrentGameOptions.MapId == MapNames.Fungle; + public static byte GetActiveMapId => GameOptionsManager.Instance.CurrentGameOptions.MapId; + public static bool IsSystemActive(SystemTypes type) + { + if (IsHideNSeek || !ShipStatus.Instance.Systems.TryGetValue(type, out var system)) + { + return false; + } + + int mapId = GetActiveMapId; + + return type switch + { + SystemTypes.Electrical when mapId != 5 => system.Cast()?.IsActive ?? false, + SystemTypes.Reactor when mapId != 2 => system.Cast()?.IsActive ?? false, + SystemTypes.Laboratory when mapId == 2 => system.Cast()?.IsActive ?? false, + SystemTypes.LifeSupp when mapId is 0 or 3 => system.Cast()?.IsActive ?? false, + SystemTypes.HeliSabotage when mapId == 4 => system.Cast()?.IsActive ?? false, + SystemTypes.Comms when mapId is 1 or 5 => system.Cast()?.IsActive ?? false, + SystemTypes.Comms => system.Cast()?.IsActive ?? false, + SystemTypes.MushroomMixupSabotage when mapId == 5 => system.Cast()?.IsActive ?? false, + _ => false + }; + } + public static bool IsCriticalSabotageActive() + { + var deathSabotages = new[] + { + SystemTypes.Reactor, + SystemTypes.Laboratory, + SystemTypes.LifeSupp, + SystemTypes.HeliSabotage, + }; + + return deathSabotages.Any(IsSystemActive); + } + public static bool IsNoneCriticalSabotageActive() + { + var noneDeathSabotages = new[] + { + SystemTypes.Electrical, + SystemTypes.Comms, + SystemTypes.MushroomMixupSabotage + }; + + return noneDeathSabotages.Any(IsSystemActive); + } + public static bool IsAnySabotageActive() + { + var allSabotages = new[] + { + SystemTypes.Electrical, + SystemTypes.Reactor, + SystemTypes.Laboratory, + SystemTypes.LifeSupp, + SystemTypes.HeliSabotage, + SystemTypes.Comms, + SystemTypes.MushroomMixupSabotage + }; + + return allSabotages.Any(IsSystemActive); + } public static bool IsInGame => InGame; public static bool IsLobby => AmongUsClient.Instance?.GameState == InnerNet.InnerNetClient.GameStates.Joined; public static bool IsInIntro => IntroCutscene.Instance != null; diff --git a/Modules/RPC.cs b/Modules/RPC.cs index 6f07b33..10c8fea 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -1,6 +1,7 @@ using BetterAmongUs.Patches; using HarmonyLib; using Hazel; +using InnerNet; namespace BetterAmongUs; @@ -55,14 +56,42 @@ internal class RPCHandlerPatch { RPC.HandleCustomRPC(__instance, callId, reader); } +} + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] +public static class MessageReaderUpdateSystemPatch +{ + public static bool Prefix(/*ShipStatus __instance,*/ [HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) + { + if (GameStates.IsHideNSeek) return false; + + var amount = MessageReader.Get(reader).ReadByte(); + if (AntiCheat.RpcUpdateSystemCheck(player, systemType, amount) != true) + { + Logger.LogCheat($"RPC canceled by Anti-Cheat: {Enum.GetName(typeof(SystemTypes), (int)systemType)} - {amount}"); + return false; + } + + return true; + } } internal static class RPC { + public static void SendBetterCheck() + { + var flag = GameStates.IsHost && Main.BetterHost.Value; + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.BetterCheck, SendOption.None, -1); + messageWriter.Write(true); + messageWriter.Write(flag); + messageWriter.Write(Main.GetVersionText().Replace(" ", "")); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + public static void SetNamePrivate(PlayerControl player, string name, PlayerControl target) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, target.GetClientId()); - writer.Write(player.NetId); + writer.Write(player.Data.NetId); writer.Write(name); AmongUsClient.Instance.FinishRpcImmediately(writer); } @@ -138,17 +167,17 @@ internal static class RPC switch (callId) { case (byte)CustomRPC.BetterCheck: - if (reader.ReadByte() == player.Data.NetId) { + var SetBetterUser = reader.ReadBoolean(); var IsBetterHost = reader.ReadBoolean(); if (!player.IsHost() && IsBetterHost) { - BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((RpcCalls)callId)} called as BetterHost"); + BetterNotificationManager.NotifyCheat(player, $"Invalid Action RPC: {Enum.GetName((CustomRPC)callId)} called as BetterHost"); break; } - player.BetterData().IsBetterUser = true; + player.BetterData().IsBetterUser = SetBetterUser; player.BetterData().IsBetterHost = IsBetterHost; } break; diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 0fc6afd..ea46f90 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -63,6 +63,18 @@ public static class Utils throw; } } + public static bool SystemTypeIsSabotage(SystemTypes type) => type is SystemTypes.Reactor + or SystemTypes.Laboratory + or SystemTypes.Comms + or SystemTypes.LifeSupp + or SystemTypes.MushroomMixupSabotage + or SystemTypes.HeliSabotage; + public static bool SystemTypeIsSabotage(int typeNum) => (SystemTypes)typeNum is SystemTypes.Reactor + or SystemTypes.Laboratory + or SystemTypes.Comms + or SystemTypes.LifeSupp + or SystemTypes.MushroomMixupSabotage + or SystemTypes.HeliSabotage; // Get players HashPuid public static string GetHashPuid(PlayerControl player) { @@ -94,6 +106,18 @@ public static class Utils } // Get name for role public static string GetRoleName(RoleTypes role) => Main.GetRoleName[(int)role]; + // Get hex color for team + public static string GetTeamHexColor(RoleTeamTypes team) + { + if (team == RoleTeamTypes.Impostor) + { + return "#f00202"; + } + else + { + return "#8cffff"; + } + } public static string Color32ToHex(Color32 color) => $"#{color.r:X2}{color.g:X2}{color.b:X2}{255:X2}"; // Disconnect client public static void DisconnectSelf(string reason, bool showReason = true) diff --git a/Patches/ClientOptionsPatch.cs b/Patches/ClientOptionsPatch.cs index 4ee396e..5702c0f 100644 --- a/Patches/ClientOptionsPatch.cs +++ b/Patches/ClientOptionsPatch.cs @@ -1,5 +1,4 @@ using HarmonyLib; -using Hazel; using UnityEngine; namespace BetterAmongUs.Patches; @@ -52,12 +51,7 @@ public static class OptionsMenuBehaviourPatch BetterHost = ClientOptionItem.Create("Better Host", Main.BetterHost, __instance, BetterHostButtonToggle, () => !toggleCheckInGamePlay("Better Host")); static void BetterHostButtonToggle() { - var flag = GameStates.IsHost && Main.BetterHost.Value; - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, unchecked((byte)CustomRPC.BetterCheck), SendOption.None, -1); - messageWriter.Write((byte)PlayerControl.LocalPlayer.NetId); - messageWriter.Write(flag); - messageWriter.Write(Main.GetVersionText().Replace(" ", "")); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + RPC.SendBetterCheck(); RPC.SyncAllNames(force: true); } @@ -66,8 +60,8 @@ public static class OptionsMenuBehaviourPatch if (BetterRoleAlgorithma == null || BetterRoleAlgorithma.ToggleButton == null) { BetterRoleAlgorithma = ClientOptionItem.Create("Better Role Algorithma", Main.BetterRoleAlgorithma, __instance, toggleCheck: () => !toggleCheckInGamePlay("Better Role Algorithma")); - } - + } + if (BetterNotifications == null || BetterNotifications.ToggleButton == null) { BetterNotifications = ClientOptionItem.Create("Better Notifications", Main.BetterNotifications, __instance, BetterNotificationsToggle); diff --git a/Patches/ClientPatch.cs b/Patches/ClientPatch.cs index 9cb7890..11d194b 100644 --- a/Patches/ClientPatch.cs +++ b/Patches/ClientPatch.cs @@ -73,6 +73,16 @@ public class ClientPatch __result = GameStates.IsHost || (GameStates.IsInGamePlay && (GameStates.IsMeeting || GameStates.IsExilling)); return false; } + [HarmonyPatch(nameof(InnerNetClient.KickPlayer))] + [HarmonyPrefix] + public static void KickPlayer_Prefix(ref int clientId, ref bool ban) + { + if (ban) + { + NetworkedPlayerInfo info = Utils.PlayerFromClientId(clientId).Data; + BetterDataManager.SaveBanList(info.FriendCode, info.Puid); + } + } } // Set text color [HarmonyPatch(typeof(CosmeticsLayer))] @@ -146,7 +156,6 @@ public class ClientPatch string extractedText = playerName.Split(new[] { "", "" }, StringSplitOptions.None)[1]; playerName = extractedText; } - } } } diff --git a/Patches/CommandsPatch.cs b/Patches/CommandsPatch.cs index 286a410..5712f37 100644 --- a/Patches/CommandsPatch.cs +++ b/Patches/CommandsPatch.cs @@ -107,7 +107,7 @@ class CommandsPatch case "dump": if (GameStates.IsInGamePlay && !GameStates.IsDev) return; - string logFilePath = Path.Combine(Environment.CurrentDirectory, "better-log.txt"); + string logFilePath = Path.Combine(BetterDataManager.filePathFolder, "better-log.txt"); string log = File.ReadAllText(logFilePath); string newLog = string.Empty; string[] logArray = log.Split(new[] { Environment.NewLine }, StringSplitOptions.None); diff --git a/Patches/GamePlayManagerPatch.cs b/Patches/GamePlayManagerPatch.cs index d366ea0..e8a3ae6 100644 --- a/Patches/GamePlayManagerPatch.cs +++ b/Patches/GamePlayManagerPatch.cs @@ -1,5 +1,8 @@ using HarmonyLib; -using Hazel; +using Newtonsoft.Json.Utilities; +using System.Text; +using TMPro; +using UnityEngine; namespace BetterAmongUs.Patches; @@ -16,6 +19,23 @@ class GamePlayManager { AntiCheat.PauseAntiCheat(); } + + // Clear unused Better Data + var keysToRemove = new List(); + + foreach (var betterInfo in PlayerControlExtensions.playerInfo) + { + if (!Main.AllPlayerControls.Any(pc => pc.Data.Puid == betterInfo.Key)) + { + keysToRemove.Add(betterInfo.Key); + } + } + + foreach (var key in keysToRemove) + { + PlayerControlExtensions.playerInfo.Remove(key); + } + } [HarmonyPatch(nameof(LobbyBehaviour.Start))] [HarmonyPostfix] @@ -26,6 +46,7 @@ class GamePlayManager if (GameStates.IsInGame) { RPC.SyncAllNames(force: true); + RPC.SendBetterCheck(); } }, 1.5f, "LobbyBehaviourPatch SyncAllNames"); } @@ -62,12 +83,206 @@ class GamePlayManager { [HarmonyPatch(nameof(GameStartManager.Update))] [HarmonyPrefix] - private static void Prefix(GameStartManager __instance) + private static void Update_Prefix(GameStartManager __instance) { - if (GameStates.IsDev) + __instance.MinPlayers = 1; + } + [HarmonyPatch(nameof(GameStartManager.Update))] + [HarmonyPostfix] + private static void Update_Postfix(GameStartManager __instance) + { + if (!GameStates.IsHost) { - __instance.MinPlayers = 1; + __instance.StartButton.gameObject.SetActive(false); + return; + } + __instance.GameStartTextParent.SetActive(false); + __instance.StartButton.gameObject.SetActive(true); + if (__instance.startState == GameStartManager.StartingStates.Countdown) + { + __instance.StartButton.buttonText.text = string.Format("Cancel: {0}", (int)__instance.countDownTimer + 1); + } + else + { + __instance.StartButton.buttonText.text = "Start Game"; + } + } + [HarmonyPatch(nameof(GameStartManager.BeginGame))] + [HarmonyPrefix] + private static bool BeginGame_Prefix(GameStartManager __instance) + { + if (__instance.startState == GameStartManager.StartingStates.Countdown) + { + SoundManager.instance.StopSound(__instance.gameStartSound); + __instance.ResetStartState(); + return false; + } + + if (Input.GetKey(KeyCode.LeftShift)) + { + __instance.startState = GameStartManager.StartingStates.Countdown; + __instance.FinallyBegin(); + return false; + } + + return true; + } + } + [HarmonyPatch(typeof(EndGameManager))] + public class EndGameManagerPatch + { + [HarmonyPatch(nameof(EndGameManager.SetEverythingUp))] + [HarmonyPostfix] + private static void SetEverythingUp_Postfix(EndGameManager __instance) + { + GameObject SummaryObj = UnityEngine.Object.Instantiate(__instance.WinText.gameObject, __instance.WinText.transform.parent.transform); + SummaryObj.name = "SummaryObj (TMP)"; + SummaryObj.transform.SetSiblingIndex(0); + Camera localCamera; + if (DestroyableSingleton.InstanceExists) + { + localCamera = DestroyableSingleton.Instance.GetComponentInChildren(); + } + else + { + localCamera = Camera.main; + } + + SummaryObj.transform.position = AspectPosition.ComputeWorldPosition(localCamera, AspectPosition.EdgeAlignments.LeftTop, new Vector3(1f, 0.2f, -5f)); + SummaryObj.transform.localScale = new Vector3(0.22f, 0.22f, 0.22f); + TextMeshPro SummaryText = SummaryObj.GetComponent(); + if (SummaryText != null) + { + SummaryText.autoSizeTextContainer = false; + SummaryText.enableAutoSizing = false; + SummaryText.lineSpacing = -25f; + SummaryText.alignment = TextAlignmentOptions.TopLeft; + SummaryText.color = Color.white; + + NetworkedPlayerInfo[] playersData = GameData.Instance.AllPlayers + .ToArray() + .OrderBy(pd => pd.Disconnected) // Disconnected players last + .ThenBy(pd => pd.IsDead) + .ThenBy(pd => !pd.Role.IsImpostor) + .ToArray(); // Dead players after live players + + string winTeam; + string winTag; + string winColor; + + switch (EndGameResult.CachedGameOverReason) + { + case GameOverReason.HumansByTask: + winTeam = "Cremates"; + winTag = "Tasks Completion"; + winColor = "#8cffff"; + break; + case GameOverReason.HumansByVote: + winTeam = "Cremates"; + winTag = "Imposters Voted Out"; + winColor = "#8cffff"; + break; + case GameOverReason.ImpostorDisconnect: + winTeam = "Cremates"; + winTag = "Impostors Disconnected"; + winColor = "#8cffff"; + break; + case GameOverReason.ImpostorByKill: + winTeam = "Imposters"; + winTag = "Crew Outnumbered"; + winColor = "#f00202"; + break; + case GameOverReason.ImpostorBySabotage: + winTeam = "Imposters"; + winTag = "Sabotage"; + winColor = "#f00202"; + break; + case GameOverReason.ImpostorByVote: + winTeam = "Imposters"; + winTag = "Crew Outnumbered"; + winColor = "#f00202"; + break; + case GameOverReason.HumansDisconnect: + winTeam = "Imposters"; + winTag = "Cremates Disconnected"; + winColor = "#f00202"; + break; + + // H&S + + case GameOverReason.HideAndSeek_ByTimer: + winTeam = "Hiders"; + winTag = "Time Out"; + winColor = "#8cffff"; + break; + case GameOverReason.HideAndSeek_ByKills: + winTeam = "Seekers"; + winTag = "No Survivors"; + winColor = "#f00202"; + break; + + + default: + winTeam = "Unknown"; + winTag = "Unknown"; + winColor = "#ffffff"; + break; + } + + string SummaryHeader = " Game Summary"; + SummaryHeader += $"\n\n{winTeam} Won" + + $"\n\nBy {winTag}"; + + StringBuilder sb = new StringBuilder(); + + foreach (var data in playersData) + { + var name = $"{data.PlayerName}"; + string playerTheme(string text) => $"{text}"; + + string roleInfo; + if (data.Role.IsImpostor) + { + roleInfo = $"({playerTheme(Utils.GetRoleName(data.RoleType))}) → {playerTheme($"Kills: {data.BetterData().RoleInfo.Kills}")}"; + } + else + { + roleInfo = $"({playerTheme(Utils.GetRoleName(data.RoleType))}) → {playerTheme($"Tasks: {data.Tasks.ToArray().Where(task => task.Complete).Count()}/{data.Tasks.Count}")}"; + } + + string deathReason; + if (data.Disconnected) + { + deathReason = "『D/C』"; + } + else if (!data.IsDead) + { + deathReason = "『Alive』"; + ; + } + else if (data.IsDead) + { + deathReason = "『Dead』"; + } + else + { + deathReason = "『Unknown』"; + } + + sb.AppendLine($"- {name} {roleInfo} {deathReason}\n"); + } + + SummaryText.text = $"{SummaryHeader}\n\n{sb}"; + } + } + [HarmonyPatch(nameof(EndGameManager.ShowButtons))] + [HarmonyPrefix] + private static bool ShowButtons_Prefix(EndGameManager __instance) + { + __instance.FrontMost.gameObject.SetActive(false); + __instance.Navigation.ShowDefaultNavigation(); + return false; } } } diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 1822659..b9a80f2 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1,5 +1,4 @@ using HarmonyLib; -using Hazel; using System.Text; using TMPro; using UnityEngine; @@ -77,7 +76,35 @@ class MeetingHudUpdatePatch if (!flag) { - SetPlayerTextInfoMeeting(pva, "Disconnected", isInfo: true); + string DisconnectText; + var playerData = GameData.Instance.GetPlayerById(pva.TargetPlayerId); + switch (playerData.BetterData().DisconnectReason) + { + case DisconnectReasons.ExitGame: + DisconnectText = "Left The Game"; + break; + case DisconnectReasons.Banned: + if (playerData.BetterData().BannedByAntiCheat) + { + DisconnectText = "Banned By Anti-Cheat>"; + } + else + { + DisconnectText = "Banned By Host"; + } + break; + case DisconnectReasons.Kicked: + DisconnectText = "Kicked By Host"; + break; + case DisconnectReasons.Hacking: + DisconnectText = "Banned By Server"; + break; + default: + DisconnectText = "Disconnected"; + break; + } + + SetPlayerTextInfoMeeting(pva, $"{DisconnectText}", isInfo: true); SetPlayerTextInfoMeeting(pva, ""); pva.transform.Find("votePlayerBase").gameObject.SetActive(false); pva.transform.Find("deadX_border").gameObject.SetActive(false); @@ -120,6 +147,10 @@ class MeetingHudUpdatePatch string RoleHexColor = target.IsImpostorTeam() ? "#ff1919" : "#8cffff"; string Role = $"{target.GetRoleName()}"; + if (!target.IsImpostorTeam() && target.myTasks.Count > 0) + { + Role += $" ({target.myTasks.ToArray().Where(task => task.IsComplete).Count()}/{target.myTasks.Count})"; + } if (!target.IsImpostorTeammate()) { if (PlayerControl.LocalPlayer.IsAlive() && target != PlayerControl.LocalPlayer) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 26f39f5..8085820 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -71,7 +71,14 @@ class PlayerControlPatch if (!GameStates.IsInGamePlay) { __instance.BetterData().TimesCalledMeeting = 0; - __instance.BetterData().HasNoisemakerNotify = false; + __instance.BetterData().RoleInfo.HasNoisemakerNotify = false; + __instance.BetterData().TimeSinceLastTask = 5f; + __instance.BetterData().LastTaskId = 999; + __instance.BetterData().RoleInfo.Kills = 0; + } + else + { + __instance.BetterData().TimeSinceLastTask += Time.deltaTime; } } @@ -103,6 +110,8 @@ class PlayerControlPatch string NewName = player.Data.PlayerName; string hashPuid = Utils.GetHashPuid(player); + string platform = Utils.GetPlatformName(player, useTag: true); + string pattern = @"^[a-zA-Z0-9#]+$"; string hashtagPattern = @"^#[0-9]{4}$"; string friendCode = player.Data.FriendCode; @@ -112,11 +121,14 @@ class PlayerControlPatch || friendCode.Contains(' ')) ? "#00f7ff" : "#ff0000"; if (string.IsNullOrEmpty(friendCode) || friendCode == "") { - friendCode = "???"; + friendCode = "No Friend Code"; friendCodeColor = "#ff0000"; } if (DataManager.Settings.Gameplay.StreamerMode == true) + { friendCode = string.Concat('*').Repeat(friendCode.Length); + platform = "Platform Hidden"; + } var sbTag = new StringBuilder(); var sbInfo = new StringBuilder(); @@ -126,12 +138,15 @@ class PlayerControlPatch var sbInfoBottom = new StringBuilder(); // Put +++ at the end of each tag - if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.SickoData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.SickoData.ContainsValue(friendCode)) - sbTag.Append("Sicko User+++"); - else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.AUMData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.AUMData.ContainsValue(friendCode)) - sbTag.Append("AUM User+++"); - else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.PlayerData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.PlayerData.ContainsValue(friendCode)) - sbTag.Append("Known Cheater+++"); + if (!player.isDummy) + { + if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.SickoData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.SickoData.ContainsValue(friendCode)) + sbTag.Append("Sicko User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.AUMData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.AUMData.ContainsValue(friendCode)) + sbTag.Append("AUM User+++"); + else if (!string.IsNullOrEmpty(hashPuid) && AntiCheat.PlayerData.ContainsKey(hashPuid) || !string.IsNullOrEmpty(friendCode) && AntiCheat.PlayerData.ContainsValue(friendCode)) + sbTag.Append("Known Cheater+++"); + } if (GameStates.IsInGame && GameStates.IsLobby && !GameStates.IsFreePlay) { @@ -145,13 +160,14 @@ class PlayerControlPatch if (player.IsDev() && !GameStates.IsInGamePlay) sbTag.Append("(Dev)+++"); - if (((player == PlayerControl.LocalPlayer && GameStates.IsHost && Main.BetterHost.Value) || player.BetterData().IsBetterHost) && !GameStates.IsInGamePlay) + if (((player == PlayerControl.LocalPlayer && GameStates.IsHost && Main.BetterHost.Value) + || (player != PlayerControl.LocalPlayer && player.BetterData().IsBetterHost && player.IsHost())) && !GameStates.IsInGamePlay) sbTag.Append("Better Host+++"); else if ((player == PlayerControl.LocalPlayer || player.BetterData().IsBetterUser) && !GameStates.IsInGamePlay) sbTag.Append("Better User+++"); sbTag.Append($"ID: {player.PlayerId}+++"); - sbTagTop.Append($"{Utils.GetPlatformName(player, useTag: true)}+++"); + sbTagTop.Append($"{platform}+++"); sbTagTop.Append($"Lv: {player.Data.PlayerLevel.ToString()}+++"); @@ -162,6 +178,10 @@ class PlayerControlPatch if (player.IsImpostorTeammate() || player == PlayerControl.LocalPlayer || !PlayerControl.LocalPlayer.IsAlive() || DebugMenu.RevealRoles) { string Role = $"{player.GetRoleName()}"; + if (!player.IsImpostorTeam() && player.myTasks.Count > 0) + { + Role += $" ({player.Data.Tasks.ToArray().Where(task => task.Complete).Count()}/{player.Data.Tasks.Count})"; + } sbTagTop.Append($"{Role}+++"); } } @@ -204,7 +224,8 @@ class PlayerControlPatch player.SetPlayerTextInfo(sbInfoTop.ToString()); player.SetPlayerTextInfo(sbInfoBottom.ToString(), isBottom: true); player.SetPlayerTextInfo(sbInfo.ToString(), isInfo: true); - } catch (Exception ex) + } + catch (Exception ex) { Logger.Error(ex.ToString()); } @@ -234,6 +255,8 @@ class PlayerControlPatch HudManager.Instance?.NotifyOfDeath(); Logger.LogPrivate($"{__instance.Data.PlayerName} Has killed {target.Data.PlayerName} as {Utils.GetRoleName(__instance.Data.RoleType)}", "EventLog"); + + __instance.BetterData().RoleInfo.Kills += 1; } [HarmonyPatch(nameof(PlayerControl.Shapeshift))] [HarmonyPostfix] diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 4b55074..6fa34fa 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -1,6 +1,5 @@ using BetterAmongUs.Patches; using HarmonyLib; -using Hazel; using InnerNet; namespace BetterAmongUs; @@ -12,6 +11,8 @@ class OnGameJoinedPatch { try { + PlayerControlExtensions.playerInfo.Clear(); + PlayerControlPatch.infotime = 0f; AntiCheat.PauseAntiCheat(); @@ -24,20 +25,6 @@ class OnGameJoinedPatch host.SetColor(host.CurrentOutfit.ColorId); } - _ = new LateTask(() => - { - // Send Better Among Us Check RPC - if (GameStates.IsInGame) - { - var flag = GameStates.IsHost && Main.BetterHost.Value; - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.BetterCheck, SendOption.None, -1); - messageWriter.Write((byte)PlayerControl.LocalPlayer.NetId); - messageWriter.Write(flag); - messageWriter.Write(Main.GetVersionText().Replace(" ", "")); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } - }, 1.5f, "OnGameJoinedPatch"); - Logger.Log($"Successfully joined {GameCode.IntToGameName(AmongUsClient.Instance.GameId)}", "OnGameJoinedPatch"); } catch { }; @@ -55,17 +42,54 @@ public static class OnPlayerJoinedPatch if (GameStates.IsInGame) { // Send Better Among Us Check RPC - var flag = GameStates.IsHost && Main.BetterHost.Value; - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, unchecked((byte)CustomRPC.BetterCheck), SendOption.None, -1); - messageWriter.Write((byte)PlayerControl.LocalPlayer.NetId); - messageWriter.Write(flag); - messageWriter.Write(Main.GetVersionText().Replace(" ", "")); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + RPC.SendBetterCheck(); RPC.SyncAllNames(force: true); if (Main.BetterHost.Value) client.Character.RpcSendHostChat(HudManagerPatch.WelcomeMessage, sendToBetterUser: false); + + // Auto ban player on ban list + var player = Utils.PlayerFromClientId(client.Id); + if (player != null) + { + string banPlayerListContent = File.ReadAllText(BetterDataManager.banPlayerListFile); + + string[] listPlayerArray = banPlayerListContent.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + + foreach (string text in listPlayerArray) + { + if (!string.IsNullOrEmpty(player.Data.FriendCode) && text.Contains(player.Data.FriendCode) + || !string.IsNullOrEmpty(Utils.GetHashPuid(player)) && text.Contains(Utils.GetHashPuid(player))) + { + player.Kick(true, $"{player.Data.PlayerName} has been banned due to being on the ban player list!"); + break; + } + } + + // Normalize and remove spaces and special characters from names + Func normalizeName = name => new string(name.Where(c => char.IsLetterOrDigit(c)).ToArray()).ToLower(); + + // Read all banned names into a HashSet with normalized names + HashSet bannedNames = new HashSet( + File.ReadLines(BetterDataManager.banNameListFile) + .Select(normalizeName) + .Where(name => !string.IsNullOrWhiteSpace(name)) + ); + + string normalizedPlayerName = normalizeName(player.Data.PlayerName); + + // Check if any banned name is a prefix of the player's normalized name + bool isNameBanned = bannedNames.Any(bannedName => + normalizedPlayerName.StartsWith(bannedName) + ); + + if (!string.IsNullOrEmpty(normalizedPlayerName) && isNameBanned) + { + player.Kick(false, $"{player.Data.PlayerName} has been kicked due to their name being on the ban name list!"); + } + + } } }, 2.5f, "OnPlayerJoinedPatch", false); } @@ -79,11 +103,23 @@ class OnPlayerLeftPatch HudManager.Instance?.NotifyOfDisconnect(data.Character); } } + +[HarmonyPatch(typeof(GameData))] +[HarmonyPatch("HandleDisconnect")] +[HarmonyPatch(MethodType.Normal)] +[HarmonyPatch(new Type[] { typeof(PlayerControl), typeof(DisconnectReasons) })] +class GameDataHandleDisconnectPatch +{ + public static void Prefix(/*GameData __instance,*/ [HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] DisconnectReasons reason) + { + player.BetterData().DisconnectReason = reason; + } +} [HarmonyPatch(typeof(GameData), nameof(GameData.ShowNotification))] class GameDataShowNotificationPatch { - public static bool Prefix(AmongUsClient __instance, ref string playerName, ref DisconnectReasons reason) + public static bool Prefix(/*GameData __instance,*/ ref string playerName, ref DisconnectReasons reason) { string ReasonText; switch (reason) diff --git a/Patches/RoleManagerPatch.cs b/Patches/RoleManagerPatch.cs index 860a07d..3f65851 100644 --- a/Patches/RoleManagerPatch.cs +++ b/Patches/RoleManagerPatch.cs @@ -23,6 +23,20 @@ public class RoleManagerPatch { if (!Main.BetterRoleAlgorithma.Value) return true; + if (!GameStates.IsHideNSeek) + { + RegularBetterRoleAssignment(); + } + else + { + HideAndSeekBetterRoleAssignment(); + } + + return false; + } + + public static void RegularBetterRoleAssignment() + { Logger.LogHeader($"Better Role Assignment Has Started", "RoleManager"); // Set roles up @@ -245,8 +259,118 @@ public class RoleManagerPatch }, 1f, "RoleManager SyncAllNames"); Logger.LogHeader($"Better Role Assignment Has Finished", "RoleManager"); + } - return false; + public static void HideAndSeekBetterRoleAssignment() + { + Logger.LogHeader($"Better Role Assignment Has Started", "RoleManager"); + + foreach (var addplayer in Main.AllPlayerControls.Where(pc => !ImpostorMultiplier.ContainsKey(Utils.GetHashPuid(pc)))) + ImpostorMultiplier[Utils.GetHashPuid(addplayer)] = 0; + + int NumImpostors = 1; + + int NumPlayers = Main.AllPlayerControls.Length; + + List Impostors = []; + List Crewmates = []; + + // Set imp from settings + int SetImpostor = GameOptionsManager.Instance.currentHideNSeekGameOptions.ImpostorPlayerID; + + if (SetImpostor >= 0) + { + var player = Utils.PlayerFromId(SetImpostor); + if (player != null) + { + Impostors.Add(player); + player.RpcSetRole(RoleTypes.Impostor); + player.roleAssigned = true; + Logger.Log($"Settings Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {player.Data.PlayerName}", "RoleManager"); + } + } + + // Override player role assignment + if (SetPlayerRole.Keys.Any()) + { + foreach (var kvp in SetPlayerRole.Where(kvp => kvp.Key != null).OrderBy(kvp => kvp.Key == PlayerControl.LocalPlayer ? 0 : 1)) + { + var player = kvp.Key; + var role = kvp.Value; + + if (role is RoleTypes.Impostor) + { + if (Impostors.Count < NumImpostors) + { + Impostors.Add(player); + player.RpcSetRole(RoleTypes.Impostor); + player.roleAssigned = true; + Logger.Log($"Override Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {player.Data.PlayerName}", "RoleManager"); + } + else continue; + } + else if (role is RoleTypes.Engineer) + { + Crewmates.Add(player); + player.RpcSetRole(RoleTypes.Engineer); + player.roleAssigned = true; + Logger.Log($"Override Assigned {Utils.GetRoleName(RoleTypes.Engineer)} role to {player.Data.PlayerName}", "RoleManager"); + } + } + } + + // Get players in random order + List players = Main.AllPlayerControls + .Where(player => !Impostors.Contains(player) && !Crewmates.Contains(player) && player.roleAssigned == false) + .ToList(); + + int n = players.Count; + while (n > 1) + { + n--; + int k = random.Next(n + 1); + PlayerControl value = players[k]; + players[k] = players[n]; + players[n] = value; + } + + // Assign roles + foreach (PlayerControl pc in players) + { + if (pc == null || pc.roleAssigned == true) continue; + + if (Impostors.Count < NumImpostors && RNG() > ImpostorMultiplier[Utils.GetHashPuid(pc)]) + { + if (!Impostors.Contains(pc)) + { + ImpostorMultiplier[Utils.GetHashPuid(pc)] += 15; + Impostors.Add(pc); + pc.RpcSetRole(RoleTypes.Impostor); + pc.roleAssigned = true; + Logger.Log($"Assigned {Utils.GetRoleName(RoleTypes.Impostor)} role to {pc.Data.PlayerName}", "RoleManager"); + } + } + else + { + if (!Crewmates.Contains(pc)) + { + ImpostorMultiplier[Utils.GetHashPuid(pc)] = 0; + Crewmates.Add(pc); + pc.RpcSetRole(RoleTypes.Engineer); + pc.roleAssigned = true; + Logger.Log($"Assigned {Utils.GetRoleName(RoleTypes.Engineer)} role to {pc.Data.PlayerName}", "RoleManager"); + } + } + } + + SetPlayerRole.Clear(); + + _ = new LateTask(() => + { + RPC.SyncAllNames(false, true, Main.BetterHost.Value); + }, 1f, "RoleManager SyncAllNames"); + + Logger.LogHeader($"Better Role Assignment Has Finished", "RoleManager"); } [HarmonyPatch(nameof(RoleManager.AssignRoleOnDeath))] @@ -254,7 +378,7 @@ public class RoleManagerPatch public static bool AssignRoleOnDeath_Prefix(/*RoleManager __instance*/ [HarmonyArgument(0)] PlayerControl player) { if (player.Data.RoleType != RoleTypes.CrewmateGhost && player.Data.RoleType != RoleTypes.ImpostorGhost) - player.BetterData().DeadDisplayRole = player.Data.RoleType; + player.BetterData().RoleInfo.DeadDisplayRole = player.Data.RoleType; if (!Main.BetterRoleAlgorithma.Value) return true; @@ -285,6 +409,8 @@ public class RoleManagerPatch foreach (var kvp in ghostRoles) { + if (player.IsImpostorTeam() && kvp.Key is RoleTypes.GuardianAngel) continue; + if (kvp.Value > 0 && RNG() <= GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(kvp.Key)) { player.RpcSetRole(kvp.Key); diff --git a/Patches/RolePatch.cs b/Patches/RolePatch.cs index ecfd18e..764ff7f 100644 --- a/Patches/RolePatch.cs +++ b/Patches/RolePatch.cs @@ -1,5 +1,4 @@ using HarmonyLib; -using Rewired; namespace BetterAmongUs.Patches; @@ -12,12 +11,12 @@ class RolePatch [HarmonyPrefix] public static bool NotifyOfDeath_Prefix(NoisemakerRole __instance) { - if (__instance.Player.BetterData().HasNoisemakerNotify) + if (__instance.Player.BetterData().RoleInfo.HasNoisemakerNotify) { return false; } - __instance.Player.BetterData().HasNoisemakerNotify = true; + __instance.Player.BetterData().RoleInfo.HasNoisemakerNotify = true; return true; } diff --git a/main.cs b/main.cs index 24d4de3..559573c 100644 --- a/main.cs +++ b/main.cs @@ -28,7 +28,7 @@ public class Main : BasePlugin public const bool IsHotFix = false; public const string PluginGuid = "com.d1gq.betteramongus"; public const string PluginVersion = "1.0.2"; - public const string ReleaseDate = "08.15.2024"; // mm/dd/yyyy + public const string ReleaseDate = "08.16.2024"; // mm/dd/yyyy public const string Github = "https://github.com/D1GQ/BetterAmongUs-Public"; public const string Discord = "https://discord.gg/vjYrXpzNAn"; @@ -125,16 +125,16 @@ public class Main : BasePlugin Harmony.PatchAll(); BetterDataManager.SetUp(); - BetterDataManager.LoadCheatData(); + BetterDataManager.LoadData(); LoadOptions(); if (File.Exists(Path.Combine($"{Environment.CurrentDirectory}/Among Us_Data/Plugins/x86", "steam_api.dll"))) File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "steam_appid.txt"), "945360"); - if (File.Exists(Path.Combine(Environment.CurrentDirectory, "better-log.txt"))) - File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "better-previous-log.txt"), File.ReadAllText(Path.Combine(Environment.CurrentDirectory, "better-log.txt"))); + if (File.Exists(Path.Combine(BetterDataManager.filePathFolder, "better-log.txt"))) + File.WriteAllText(Path.Combine(BetterDataManager.filePathFolder, "better-previous-log.txt"), File.ReadAllText(Path.Combine(BetterDataManager.filePathFolder, "better-log.txt"))); - File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "better-log.txt"), ""); + File.WriteAllText(Path.Combine(BetterDataManager.filePathFolder, "better-log.txt"), ""); BetterAmongUs.Logger.Log("Better Among Us successfully loaded!"); // Set up debug menu