From a719a42c7ffeee1d1bb526251e7dbb4ee687c2e8 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 25 May 2025 11:33:24 +0200 Subject: [PATCH] fix lots of dumb stuff --- GameMode/CaptureTheFlag.cs | 2 +- GameMode/KingOfTheZones.cs | 2 +- Modules/AntiBlackout.cs | 352 +++++------------------------- Modules/CustomRpcSender.cs | 4 +- Modules/ExtendedPlayerControl.cs | 80 +++---- Modules/SpamManager.cs | 24 +- Modules/Utils.cs | 4 +- Patches/ChatCommandPatch.cs | 2 +- Patches/ExilePatch.cs | 88 ++++---- Patches/GameOptionsMenuPatch.cs | 2 + Patches/IntroPatch.cs | 22 +- Patches/MeetingHudPatch.cs | 2 +- Patches/OnGameStartedPatch.cs | 6 +- Patches/PhantomRolePatch.cs | 16 +- Patches/PlayerControlPatch.cs | 24 +- Patches/PlayerJoinAndLeftPatch.cs | 1 - Patches/SabotageSystemPatch.cs | 2 + Roles/Neutral/PlagueDoctor.cs | 46 ++-- 18 files changed, 225 insertions(+), 454 deletions(-) diff --git a/GameMode/CaptureTheFlag.cs b/GameMode/CaptureTheFlag.cs index a32944384..af2386f0f 100644 --- a/GameMode/CaptureTheFlag.cs +++ b/GameMode/CaptureTheFlag.cs @@ -420,7 +420,7 @@ public static class CaptureTheFlag var pc1 = id1.GetPlayer(); if (pc1 == null) continue; - int targetClientId = pc1.GetClientId(); + int targetClientId = pc1.OwnerId; if (!senders.TryGetValue(id1, out CustomRpcSender sender) || !hasValue.ContainsKey(id1)) continue; diff --git a/GameMode/KingOfTheZones.cs b/GameMode/KingOfTheZones.cs index e057e65db..ead799b3b 100644 --- a/GameMode/KingOfTheZones.cs +++ b/GameMode/KingOfTheZones.cs @@ -331,7 +331,7 @@ public static class KingOfTheZones try { - int targetClientId = player.GetClientId(); + int targetClientId = player.OwnerId; PlayerTeams.DoIf( x => x.Key != id && x.Value == team, // ReSharper disable once AccessToModifiedClosure diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index a7899a132..0c7c05b82 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -1,338 +1,106 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using AmongUs.GameOptions; -using EHR.Modules; using EHR.Patches; using Hazel; namespace EHR; -// Most code is from https://github.com/EnhancedNetwork/TownofHost-Enhanced, as stated in the README credits - public static class AntiBlackout { public static bool SkipTasks; - private static Dictionary IsDeadCache = []; - private static readonly LogHandler Logger = EHR.Logger.Handler("AntiBlackout"); - private static PlayerControl DummyImp; + private static Dictionary<(byte SeerID, byte TargetID), (RoleTypes RoleType, CustomRoles CustomRole)> CachedRoleMap = []; - private static bool IsCached { get; set; } - - public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") + // Optimally, there's 1 alive impostor and at least 2 alive crewmates in everyone's POV. + // We force this to prevent black screens after meetings. + // If there are only 2 alive players, we revive 1 additional crewmate. + public static void SetOptimalRoleTypesToPreventBlackScreen() { + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default || PlayerControl.AllPlayerControls.Count <= 2) return; + SkipTasks = true; - RevivePlayersAndSetDummyImp(); - Logger.Info($"SetIsDead is called from {callerMethodName}"); - if (IsCached) return; - IsDeadCache.Clear(); + CachedRoleMap = StartGameHostPatch.RpcSetRoleReplacer.RoleMap.ToDictionary(x => (x.Key.SeerID, x.Key.TargetID), x => (x.Value.RoleType, x.Value.CustomRole)); - foreach (NetworkedPlayerInfo info in GameData.Instance.AllPlayers) - { - if (info == null) continue; - IsDeadCache[info.PlayerId] = (info.IsDead, info.Disconnected); - info.IsDead = false; - info.Disconnected = false; - } - - IsCached = true; - if (doSend) SendGameData(); - } - - private static void RevivePlayersAndSetDummyImp() - { - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default || PlayerControl.AllPlayerControls.Count < 2) return; - - var players = Main.AllAlivePlayerControls; + PlayerControl[] players = Main.AllAlivePlayerControls; if (CheckForEndVotingPatch.TempExiledPlayer != null) players = players.Where(x => x.PlayerId != CheckForEndVotingPatch.TempExiledPlayer.PlayerId).ToArray(); - DummyImp = players.MinBy(x => x.PlayerId); - if (DummyImp == null) return; + PlayerControl dummyImp = players.MinBy(x => x.PlayerId); - foreach (PlayerControl seer in Main.AllPlayerControls) + if (players.Length == 2) { - if (seer.IsModdedClient()) continue; - - if (Options.CurrentGameMode is CustomGameMode.Speedrun or CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.CaptureTheFlag or CustomGameMode.KingOfTheZones) - { - seer.RpcSetRoleDesync(RoleTypes.Impostor, seer.OwnerId); - - foreach (PlayerControl target in Main.AllPlayerControls) - { - if (target.PlayerId != seer.PlayerId) - target.RpcSetRoleDesync(RoleTypes.Crewmate, seer.OwnerId); - } - } - else - { - RoleTypes selfRoleType = seer.GetRoleTypes(); - bool seerIsAliveAndHasKillButton = seer.HasKillButton() && seer.IsAlive() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek; - - foreach (PlayerControl target in Main.AllPlayerControls) - { - if (seerIsAliveAndHasKillButton) - target.RpcSetRoleDesync(target.PlayerId != seer.PlayerId ? RoleTypes.Crewmate : selfRoleType, seer.GetClientId()); - else - target.RpcSetRoleDesync(target.PlayerId == DummyImp.PlayerId ? RoleTypes.Impostor : RoleTypes.Crewmate, seer.GetClientId()); - } - } - } - } - - public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") - { - Logger.Info($"RestoreIsDead is called from {callerMethodName}"); - - foreach (NetworkedPlayerInfo info in GameData.Instance.AllPlayers) - { - if (info == null) continue; - - if (IsDeadCache.TryGetValue(info.PlayerId, out (bool IsDead, bool Disconnected) val)) - { - info.IsDead = val.IsDead; - info.Disconnected = val.Disconnected; - } - } - - IsDeadCache.Clear(); - IsCached = false; - - if (doSend) - { - SendGameData(); - LateTask.New(RestoreIsDeadByExile, 0.3f + Utils.CalculatePingDelay(), "AntiBlackOut_RestoreIsDeadByExile"); - } - } - - private static void RestoreIsDeadByExile() - { - var sender = CustomRpcSender.Create("AntiBlackout RestoreIsDeadByExile", SendOption.Reliable); - var hasValue = false; - - foreach (PlayerControl player in Main.AllPlayerControls) - { - if (player.Data.IsDead && !player.Data.Disconnected) - { - player.Exiled(); - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.Exiled); - sender.EndRpc(); - hasValue = true; - } - } - - sender.SendMessage(!hasValue); - } - - public static void SendGameData([CallerMemberName] string callerMethodName = "") - { - Logger.Info($"SendGameData is called from {callerMethodName}"); - - foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) - { - playerInfo.MarkDirty(); + PlayerControl revived = Main.AllPlayerControls.Where(x => !x.IsAlive() && !x.Data.Disconnected && x.PlayerId != CheckForEndVotingPatch.TempExiledPlayer?.PlayerId).MinBy(x => x.PlayerId); + revived.Data.IsDead = false; + revived.Data.MarkDirty(); AmongUsClient.Instance.SendAllStreamedObjects(); + revived.RpcChangeRoleBasis(CustomRoles.CrewmateEHR, forced: true); } + + dummyImp.RpcChangeRoleBasis(CustomRoles.ImpostorEHR, forced: true); + players.Without(dummyImp).Do(x => x.RpcChangeRoleBasis(CustomRoles.CrewmateEHR, forced: true)); } - public static void OnDisconnect(NetworkedPlayerInfo player) + // After the ejection screen, we revert the role types to their actual values. + public static void RevertToActualRoleTypes() { - // Execution conditions: Client is the host, IsDead is overridden, player is already disconnected - if (!AmongUsClient.Instance.AmHost || !IsCached || !player.Disconnected) return; - IsDeadCache[player.PlayerId] = (true, true); - RevivePlayersAndSetDummyImp(); - player.IsDead = player.Disconnected = false; - SendGameData(); - } + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default || CachedRoleMap.Count == 0) return; - public static void AfterMeetingTasks() - { - try + // Set the temporarily revived crewmate back to dead. + foreach (PlayerControl pc in Main.AllPlayerControls) { - LateTask.New(() => + if (!pc.Data.IsDead && !pc.Data.Disconnected && !pc.IsAlive()) { - var sender = CustomRpcSender.Create("AntiBlackout.SetDeadAfterMeetingTasks", SendOption.Reliable); - var hasValue = false; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (!pc.IsAlive()) - { - pc.Exiled(); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.Exiled); - sender.EndRpc(); - hasValue = true; - } - } - - sender.SendMessage(!hasValue); - }, 0.3f + Utils.CalculatePingDelay(), "AntiBlackout.AfterMeetingTasks"); + pc.Data.IsDead = true; + pc.Data.MarkDirty(); + AmongUsClient.Instance.SendAllStreamedObjects(); + } } - catch (Exception e) { Utils.ThrowException(e); } - } - public static void SetRealPlayerRoles() - { - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - - var sender = CustomRpcSender.Create("AntiBlackout.SetRealPlayerRoles", SendOption.Reliable); - var hasValue = false; - - List selfExiled = []; - - switch (Options.CurrentGameMode) + // Reset the role types for all players. + foreach (((byte seerId, byte targetId), (RoleTypes roleType, CustomRoles _)) in CachedRoleMap) { - case CustomGameMode.Standard: - case CustomGameMode.HideAndSeek: - { - foreach (((byte seerId, byte targetId), (RoleTypes roletype, _)) in StartGameHostPatch.RpcSetRoleReplacer.RoleMap) - { - PlayerControl seer = seerId.GetPlayer(); - PlayerControl target = targetId.GetPlayer(); + PlayerControl seer = seerId.GetPlayer(); + PlayerControl target = targetId.GetPlayer(); + if (seer == null || target == null) continue; - if (seer == null || target == null) continue; - - bool self = seerId == targetId; - bool dead = target.Data.IsDead; - RoleTypes changedRoleType = roletype; - - if (dead) - { - if (self) - { - selfExiled.Add(seer); - - if (target.HasGhostRole()) changedRoleType = RoleTypes.GuardianAngel; - else if (target.Is(Team.Impostor) || target.HasDesyncRole()) changedRoleType = RoleTypes.ImpostorGhost; - else changedRoleType = RoleTypes.CrewmateGhost; - } - else - changedRoleType = roletype is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; - } - - target.RpcSetRoleDesync(changedRoleType, seer.OwnerId); - } - - foreach (PlayerControl pc in selfExiled) - { - pc.Exiled(); - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.Exiled); - sender.EndRpc(); - hasValue = true; - - if (!pc.IsModdedClient() && pc.PlayerId == ExileControllerWrapUpPatch.LastExiled?.PlayerId) - { - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.MurderPlayer, pc.OwnerId); - sender.WriteNetObject(pc); - sender.Write((int)MurderResultFlags.Succeeded); - sender.EndRpc(); - - pc.ReactorFlash(0.2f); - } - - RestartMessageIfTooLong(); - } - - break; - } - case CustomGameMode.Speedrun: - case CustomGameMode.MoveAndStop: - case CustomGameMode.HotPotato: - case CustomGameMode.NaturalDisasters: - case CustomGameMode.RoomRush: - case CustomGameMode.TheMindGame: - case CustomGameMode.Quiz: - { - foreach (PlayerControl pc in Main.AllPlayerControls) - { - pc.RpcSetRoleDesync(Speedrun.CanKill.Contains(pc.PlayerId) ? RoleTypes.Impostor : RoleTypes.Crewmate, pc.OwnerId); - - if (pc.IsModdedClient()) continue; - - if (!pc.IsAlive()) - { - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.MurderPlayer, pc.OwnerId); - sender.WriteNetObject(pc); - sender.Write((int)MurderResultFlags.Succeeded); - sender.EndRpc(); - - hasValue = true; - RestartMessageIfTooLong(); - - pc.ReactorFlash(0.2f); - } - } - - break; - } - case CustomGameMode.FFA: - case CustomGameMode.SoloKombat: - case CustomGameMode.CaptureTheFlag: - case CustomGameMode.KingOfTheZones: - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(DummyImp.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable); - writer.Write((ushort)RoleTypes.Crewmate); - writer.Write(true); - AmongUsClient.Instance.FinishRpcImmediately(writer); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) - pc.RpcSetRoleDesync(RoleTypes.Impostor, pc.OwnerId); - else - { - if (pc.IsModdedClient()) continue; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.MurderPlayer, pc.OwnerId); - sender.WriteNetObject(pc); - sender.Write((int)MurderResultFlags.Succeeded); - sender.EndRpc(); - - hasValue = true; - RestartMessageIfTooLong(); - - pc.ReactorFlash(0.2f); - } - } - - break; - } + if (target.IsAlive()) target.RpcSetRoleDesync(roleType, seer.OwnerId); + else target.RpcSetRoleDesync(target.HasGhostRole() ? RoleTypes.GuardianAngel : roleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost, seer.OwnerId); } - sender.SendMessage(!hasValue); + // Reset the role map to the original values. + StartGameHostPatch.RpcSetRoleReplacer.RoleMap = CachedRoleMap.ToDictionary(x => (x.Key.SeerID, x.Key.TargetID), x => (x.Value.RoleType, x.Value.CustomRole)); + CachedRoleMap = []; LateTask.New(() => { foreach (PlayerControl pc in Main.AllPlayerControls) { - pc.RpcResetAbilityCooldown(); - pc.SetKillCooldown(); + if (pc.IsAlive()) + { + // Due to the role base change, we need to reset the cooldowns for abilities. + pc.RpcResetAbilityCooldown(); + pc.SetKillCooldown(); + } + else + { + // Ensure that the players who are considered dead by the mod are actually dead in the game. + pc.Exiled(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(pc.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } } - }, 0.2f, log: false); - return; - - void RestartMessageIfTooLong() - { - if (sender.stream.Length > 400) + // Only execute AfterMeetingTasks after everything is reset. + LateTask.New(() => { - sender.SendMessage(); - sender = CustomRpcSender.Create("AntiBlackout.SetRealPlayerRoles", SendOption.Reliable); - hasValue = false; - } - } - } - - public static void ResetAfterMeeting() - { - LateTask.New(() => SkipTasks = false, 1f, "Reset Blackout"); + SkipTasks = false; + ExileControllerWrapUpPatch.AfterMeetingTasks(); + }, Utils.CalculatePingDelay() * 2f, "Reset SkipTasks after SetRealPlayerRoles"); + }, 0.2f, "SetRealPlayerRoles - Reset Cooldowns"); } public static void Reset() { - Logger.Info("==Reset=="); - IsDeadCache ??= []; - IsDeadCache.Clear(); - IsCached = false; + Logger.Info("==Reset==", "AntiBlackout"); + CachedRoleMap = []; SkipTasks = false; } } \ No newline at end of file diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index e4d1bde52..812cc333a 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -411,7 +411,7 @@ public static class CustomRpcSenderExtensions public static void RpcSetName(this CustomRpcSender sender, PlayerControl player, string name, PlayerControl seer = null) { bool seerIsNull = seer == null; - int targetClientId = seerIsNull ? -1 : seer.GetClientId(); + int targetClientId = seerIsNull ? -1 : seer.OwnerId; name = name.Replace("color=", string.Empty); @@ -439,7 +439,7 @@ public static class CustomRpcSenderExtensions { if (sender == null || physics == null) return false; - int clientId = seer.GetClientId(); + int clientId = seer.OwnerId; if (AmongUsClient.Instance.ClientId == clientId) { diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index d43233a44..4ebb5ba4d 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -230,7 +230,7 @@ internal static class ExtendedPlayerControl // By TommyXL public static void RpcSetPetDesync(this PlayerControl player, string petId, PlayerControl seer) { - int clientId = seer.GetClientId(); + int clientId = seer.OwnerId; if (clientId == -1) return; if (AmongUsClient.Instance.ClientId == clientId) @@ -325,49 +325,55 @@ internal static class ExtendedPlayerControl } // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs - public static bool RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false, CustomRpcSender sender = null) + public static bool RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false, CustomRpcSender sender = null, bool forced = false) { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null || !player.IsAlive()) return false; - - if (AntiBlackout.SkipTasks || ExileController.Instance) + if (!forced) { - StackTrace stackTrace = new(1, true); - MethodBase callerMethod = stackTrace.GetFrame(0)?.GetMethod(); - string callerMethodName = callerMethod?.Name; - string callerClassName = callerMethod?.DeclaringType?.FullName; - Logger.Warn($"{callerClassName}.{callerMethodName} tried to change the role basis of {player.GetNameWithRole()} during anti-blackout processing or ejection screen showing, delaying the code to run after these tasks are complete", "RpcChangeRoleBasis"); - Main.Instance.StartCoroutine(DelayBasisChange()); - return false; + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null || !player.IsAlive()) return false; - IEnumerator DelayBasisChange() + if (AntiBlackout.SkipTasks || ExileController.Instance) { - while (AntiBlackout.SkipTasks || ExileController.Instance) yield return null; - yield return new WaitForSeconds(1f); - Logger.Msg($"Now that the anti-blackout processing or ejection screen showing is complete, the role basis of {player.GetNameWithRole()} will be changed", "RpcChangeRoleBasis"); - player.RpcChangeRoleBasis(newCustomRole, loggerRoleMap); + StackTrace stackTrace = new(1, true); + MethodBase callerMethod = stackTrace.GetFrame(0)?.GetMethod(); + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + Logger.Warn($"{callerClassName}.{callerMethodName} tried to change the role basis of {player.GetNameWithRole()} during anti-blackout processing or ejection screen showing, delaying the code to run after these tasks are complete", "RpcChangeRoleBasis"); + Main.Instance.StartCoroutine(DelayBasisChange()); + return false; + + IEnumerator DelayBasisChange() + { + while (AntiBlackout.SkipTasks || ExileController.Instance) yield return null; + yield return new WaitForSeconds(1f); + Logger.Msg($"Now that the anti-blackout processing or ejection screen showing is complete, the role basis of {player.GetNameWithRole()} will be changed", "RpcChangeRoleBasis"); + player.RpcChangeRoleBasis(newCustomRole, loggerRoleMap); + } } } byte playerId = player.PlayerId; - int playerClientId = player.GetClientId(); + int playerClientId = player.OwnerId; CustomRoles playerRole = Utils.GetRoleMap(playerId).CustomRole; RoleTypes newRoleType = newCustomRole.GetRoleTypes(); RoleTypes remeberRoleType; - newRoleType = Options.CurrentGameMode switch + if (!forced) { - CustomGameMode.Speedrun when newCustomRole == CustomRoles.Runner => Speedrun.CanKill.Contains(playerId) ? RoleTypes.Impostor : RoleTypes.Crewmate, - CustomGameMode.Standard when StartGameHostPatch.BasisChangingAddons.FindFirst(x => x.Value.Contains(playerId), out KeyValuePair> kvp) => kvp.Key switch + newRoleType = Options.CurrentGameMode switch { - CustomRoles.Bloodlust when newRoleType is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist => RoleTypes.Impostor, - CustomRoles.Nimble when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Engineer, - CustomRoles.Physicist when newRoleType == RoleTypes.Crewmate => RoleTypes.Scientist, - CustomRoles.Finder when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Tracker, - CustomRoles.Noisy when newRoleType == RoleTypes.Crewmate => RoleTypes.Noisemaker, + CustomGameMode.Speedrun when newCustomRole == CustomRoles.Runner => Speedrun.CanKill.Contains(playerId) ? RoleTypes.Impostor : RoleTypes.Crewmate, + CustomGameMode.Standard when StartGameHostPatch.BasisChangingAddons.FindFirst(x => x.Value.Contains(playerId), out KeyValuePair> kvp) => kvp.Key switch + { + CustomRoles.Bloodlust when newRoleType is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist => RoleTypes.Impostor, + CustomRoles.Nimble when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Engineer, + CustomRoles.Physicist when newRoleType == RoleTypes.Crewmate => RoleTypes.Scientist, + CustomRoles.Finder when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Tracker, + CustomRoles.Noisy when newRoleType == RoleTypes.Crewmate => RoleTypes.Noisemaker, + _ => newRoleType + }, _ => newRoleType - }, - _ => newRoleType - }; + }; + } bool oldRoleIsDesync = playerRole.IsDesyncRole(); bool newRoleIsDesync = newCustomRole.IsDesyncRole() || player.Is(CustomRoles.Bloodlust); @@ -384,7 +390,7 @@ internal static class ExtendedPlayerControl { foreach (PlayerControl seer in Main.AllPlayerControls) { - int seerClientId = seer.GetClientId(); + int seerClientId = seer.OwnerId; if (seerClientId == -1) continue; bool seerIsHost = seer.IsHost(); @@ -434,7 +440,7 @@ internal static class ExtendedPlayerControl { foreach (PlayerControl seer in Main.AllPlayerControls) { - int seerClientId = seer.GetClientId(); + int seerClientId = seer.OwnerId; if (seerClientId == -1) continue; bool self = playerId == seer.PlayerId; @@ -483,7 +489,7 @@ internal static class ExtendedPlayerControl foreach (PlayerControl seer in Main.AllPlayerControls) { - int seerClientId = seer.GetClientId(); + int seerClientId = seer.OwnerId; if (seerClientId == -1) continue; if ((playerIsDesync || seer.HasDesyncRole()) && seer.PlayerId != playerId) @@ -516,7 +522,7 @@ internal static class ExtendedPlayerControl } } - Logger.Info($"{player.GetNameWithRole()}'s role basis was changed to {newRoleType} ({newCustomRole}) (from role: {playerRole}) - oldRoleIsDesync: {oldRoleIsDesync}, newRoleIsDesync: {newRoleIsDesync}", "RpcChangeRoleBasis"); + if (!forced) Logger.Info($"{player.GetNameWithRole()}'s role basis was changed to {newRoleType} ({newCustomRole}) (from role: {playerRole}) - oldRoleIsDesync: {oldRoleIsDesync}, newRoleIsDesync: {newRoleIsDesync}", "RpcChangeRoleBasis"); Main.ChangedRole = true; @@ -544,7 +550,7 @@ internal static class ExtendedPlayerControl { if (physics == null) return; - int clientId = seer.GetClientId(); + int clientId = seer.OwnerId; if (AmongUsClient.Instance.ClientId == clientId) { @@ -560,7 +566,7 @@ internal static class ExtendedPlayerControl public static void RpcStartAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) { - int clientId = seer.GetClientId(); + int clientId = seer.OwnerId; if (AmongUsClient.Instance.ClientId == clientId) { @@ -987,7 +993,7 @@ internal static class ExtendedPlayerControl sender.RpcDesyncRepairSystem(pc, systemtype, 128); - int targetClientId = pc.GetClientId(); + int targetClientId = pc.OwnerId; var ghostPos = dummyGhost.Pos(); var pcPos = pc.Pos(); var timer = Math.Max(Main.KillTimers[pc.PlayerId], 0.1f); @@ -1135,7 +1141,7 @@ internal static class ExtendedPlayerControl if (target == null || seer == null) return; - int seerClientId = seer.GetClientId(); + int seerClientId = seer.OwnerId; if (seerClientId == -1) return; byte cnt = ++target.scannerCount; diff --git a/Modules/SpamManager.cs b/Modules/SpamManager.cs index 2fd5159d0..9ae86d0b7 100644 --- a/Modules/SpamManager.cs +++ b/Modules/SpamManager.cs @@ -92,20 +92,20 @@ public static class SpamManager { if (ContainsStart(text) && GameStates.IsLobby) { - if (!Main.SayStartTimes.ContainsKey(player.GetClientId())) Main.SayStartTimes.Add(player.GetClientId(), 0); + if (!Main.SayStartTimes.ContainsKey(player.GetClientId())) Main.SayStartTimes.Add(player.OwnerId, 0); - Main.SayStartTimes[player.GetClientId()]++; - msg = string.Format(GetString("Message.WarnWhoSayStart"), name, Main.SayStartTimes[player.GetClientId()]); + Main.SayStartTimes[player.OwnerId]++; + msg = string.Format(GetString("Message.WarnWhoSayStart"), name, Main.SayStartTimes[player.OwnerId]); - if (Main.SayStartTimes[player.GetClientId()] > Options.AutoKickStartTimes.GetInt()) + if (Main.SayStartTimes[player.OwnerId] > Options.AutoKickStartTimes.GetInt()) { - msg = string.Format(GetString("Message.KickStartAfterWarn"), name, Main.SayStartTimes[player.GetClientId()]); + msg = string.Format(GetString("Message.KickStartAfterWarn"), name, Main.SayStartTimes[player.OwnerId]); kick = true; } if (msg != string.Empty && msg != "") Utils.SendMessage(msg, sendOption: SendOption.None); - if (kick) AmongUsClient.Instance.KickPlayer(player.GetClientId(), Options.AutoKickStartAsBan.GetBool()); + if (kick) AmongUsClient.Instance.KickPlayer(player.OwnerId, Options.AutoKickStartAsBan.GetBool()); return true; } @@ -119,14 +119,14 @@ public static class SpamManager if (Options.AutoKickStopWords.GetBool()) { - if (!Main.SayBanwordsTimes.ContainsKey(player.GetClientId())) Main.SayBanwordsTimes.Add(player.GetClientId(), 0); + if (!Main.SayBanwordsTimes.ContainsKey(player.GetClientId())) Main.SayBanwordsTimes.Add(player.OwnerId, 0); - Main.SayBanwordsTimes[player.GetClientId()]++; - msg = string.Format(GetString("Message.WarnWhoSayBanWordTimes"), name, Main.SayBanwordsTimes[player.GetClientId()]); + Main.SayBanwordsTimes[player.OwnerId]++; + msg = string.Format(GetString("Message.WarnWhoSayBanWordTimes"), name, Main.SayBanwordsTimes[player.OwnerId]); - if (Main.SayBanwordsTimes[player.GetClientId()] > Options.AutoKickStopWordsTimes.GetInt()) + if (Main.SayBanwordsTimes[player.OwnerId] > Options.AutoKickStopWordsTimes.GetInt()) { - msg = string.Format(GetString("Message.KickWhoSayBanWordAfterWarn"), name, Main.SayBanwordsTimes[player.GetClientId()]); + msg = string.Format(GetString("Message.KickWhoSayBanWordAfterWarn"), name, Main.SayBanwordsTimes[player.OwnerId]); kick = true; } } @@ -142,7 +142,7 @@ public static class SpamManager } } - if (kick) AmongUsClient.Instance.KickPlayer(player.GetClientId(), Options.AutoKickStopWordsAsBan.GetBool()); + if (kick) AmongUsClient.Instance.KickPlayer(player.OwnerId, Options.AutoKickStopWordsAsBan.GetBool()); return true; } diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 2303831a5..45cc289ba 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -1769,7 +1769,7 @@ public static class Utils return writer; } - int targetClientId = sendTo == byte.MaxValue ? -1 : receiver.GetClientId(); + int targetClientId = sendTo == byte.MaxValue ? -1 : receiver.OwnerId; if (writer == null || writer.CurrentState == CustomRpcSender.State.Finished) writer = CustomRpcSender.Create("Utils.SendMessage(1)", sendOption); @@ -3157,8 +3157,6 @@ public static class Utils Camouflage.CheckCamouflage(); - AntiBlackout.AfterMeetingTasks(); - foreach (PlayerControl pc in Main.AllPlayerControls) { if (pc.IsAlive()) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 5ea213996..ffbf2c86a 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -3253,7 +3253,7 @@ internal static class ChatUpdatePatch private static bool SendMessage(PlayerControl player, string msg, byte sendTo, string title, ref CustomRpcSender sender) { - int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); + int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).OwnerId; string name = player.Data.PlayerName; diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 7ead3ae0a..3401d5574 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using AmongUs.Data; using EHR.AddOns.Crewmate; @@ -20,8 +21,6 @@ internal static class ExileControllerWrapUpPatch var decidedWinner = false; if (!AmongUsClient.Instance.AmHost) return; - AntiBlackout.RestoreIsDead(false); - if (!Collector.CollectorWin(false) && exiled != null) { exiled.IsDead = true; @@ -128,47 +127,8 @@ internal static class ExileControllerWrapUpPatch LateTask.New(() => { if (GameStates.IsEnded) return; - - AntiBlackout.SendGameData(); - AntiBlackout.SetRealPlayerRoles(); - }, Options.CurrentGameMode == CustomGameMode.Standard ? 0.5f : 1.4f, "Restore IsDead Task"); - - LateTask.New(AntiBlackout.ResetAfterMeeting, 0.6f, "ResetAfterMeeting"); - - LateTask.New(() => - { - if (GameStates.IsEnded) return; - - var sender = CustomRpcSender.Create("ExileControllerWrapUpPatch.WrapUpFinalizer", SendOption.Reliable); - Main.AfterMeetingDeathPlayers.Do(x => sender.RpcExileV2(x.Key.GetPlayer())); - sender.SendMessage(Main.AfterMeetingDeathPlayers.Count == 0); - - foreach ((byte id, PlayerState.DeathReason deathReason) in Main.AfterMeetingDeathPlayers) - { - var player = id.GetPlayer(); - var state = Main.PlayerStates[id]; - - Logger.Info($"{Main.AllPlayerNames[id]} ({state.MainRole}) died with {deathReason}", "AfterMeetingDeath"); - - state.deathReason = deathReason; - state.SetDead(); - - if (player == null) continue; - - if (deathReason == PlayerState.DeathReason.Suicide) - player.SetRealKiller(player, true); - - Utils.AfterPlayerDeathTasks(player); - } - - Main.AfterMeetingDeathPlayers.Clear(); - - Utils.AfterMeetingTasks(); - Utils.SyncAllSettings(); - Utils.CheckAndSetVentInteractions(); - - Main.Instance.StartCoroutine(Utils.NotifyEveryoneAsync(speed: 5)); - }, 1f, "AfterMeetingDeathPlayers Task"); + AntiBlackout.RevertToActualRoleTypes(); + }, Math.Max(0.5f, Utils.CalculatePingDelay() * 2f), "Revert AntiBlackout Measures"); } GameStates.AlreadyDied |= !Utils.IsAllAlive; @@ -216,6 +176,41 @@ internal static class ExileControllerWrapUpPatch }, 3f, log: false); } + public static void AfterMeetingTasks() + { + if (GameStates.IsEnded) return; + + var sender = CustomRpcSender.Create("ExileControllerWrapUpPatch.WrapUpFinalizer", SendOption.Reliable); + Main.AfterMeetingDeathPlayers.Do(x => sender.RpcExileV2(x.Key.GetPlayer())); + sender.SendMessage(Main.AfterMeetingDeathPlayers.Count == 0); + + foreach ((byte id, PlayerState.DeathReason deathReason) in Main.AfterMeetingDeathPlayers) + { + var player = id.GetPlayer(); + var state = Main.PlayerStates[id]; + + Logger.Info($"{Main.AllPlayerNames[id]} ({state.MainRole}) died with {deathReason}", "AfterMeetingDeath"); + + state.deathReason = deathReason; + state.SetDead(); + + if (player == null) continue; + + if (deathReason == PlayerState.DeathReason.Suicide) + player.SetRealKiller(player, true); + + Utils.AfterPlayerDeathTasks(player); + } + + Main.AfterMeetingDeathPlayers.Clear(); + + Utils.AfterMeetingTasks(); + Utils.SyncAllSettings(); + Utils.CheckAndSetVentInteractions(); + + Main.Instance.StartCoroutine(Utils.NotifyEveryoneAsync(speed: 5)); + } + [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] private static class BaseExileControllerPatch { @@ -226,12 +221,13 @@ internal static class ExileControllerWrapUpPatch } } - [HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))] + [HarmonyPatch(typeof(AirshipExileController._WrapUpAndSpawn_d__11), nameof(AirshipExileController._WrapUpAndSpawn_d__11.MoveNext))] private static class AirshipExileControllerPatch { - public static void Postfix(AirshipExileController __instance) + public static void Postfix(AirshipExileController._WrapUpAndSpawn_d__11 __instance, ref bool __result) { - try { WrapUpPostfix(__instance.initData.networkedPlayer); } + if (__result) return; + try { WrapUpPostfix(__instance.__4__this.initData.networkedPlayer); } finally { WrapUpFinalizer(); } } } diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index 77e71246d..9a00a4368 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -361,6 +361,8 @@ public static class GameOptionsMenuPatch switch (item) { + // ToggleOption doesn't work for Steam users.... no idea why + // So instead, we use a StringOption with two values and a button to swap it case BooleanOptionItem booleanOptionItem: var stringGameSettingBoolean = ScriptableObject.CreateInstance(); stringGameSettingBoolean.Type = OptionTypes.String; diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index 828511a7a..e3c63b4bd 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -12,6 +12,22 @@ using static EHR.Translator; namespace EHR; +// Patch for non-host modded clients to ensure that the intro cutscene is shown correctly +// and GameStates.InGame is set to true +[HarmonyPatch(typeof(IntroCutscene._ShowRole_d__41), nameof(IntroCutscene._ShowRole_d__41.MoveNext))] +static class ShowRoleMoveNextPatch +{ + public static void Postfix(IntroCutscene._ShowRole_d__41 __instance) + { + if (AmongUsClient.Instance.AmHost) return; + + GameStates.InGame = true; + SetUpRoleTextPatch.Postfix(__instance.__4__this); + } +} + +// For some reason, IntroCutScene.ShowRole is not called in the base game with this exact same code, +// so we need to patch HudManager.CoShowIntro for the host entirely to ensure that the intro is shown correctly [HarmonyPatch(typeof(HudManager), nameof(HudManager.CoShowIntro))] static class CoShowIntroPatch { @@ -1067,7 +1083,11 @@ internal static class IntroCutsceneDestroyPatch sender.SendMessage(); - AntiBlackout.SendGameData(); + foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) + { + playerInfo.MarkDirty(); + AmongUsClient.Instance.SendAllStreamedObjects(); + } }, 0.3f, "Grant Pet For Everyone"); LateTask.New(() => diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index d69cbe013..2329e9bc3 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -1197,7 +1197,7 @@ internal static class MeetingHudOnDestroyPatch if (AmongUsClient.Instance.AmHost) { - AntiBlackout.SetIsDead(); + AntiBlackout.SetOptimalRoleTypesToPreventBlackScreen(); RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned.Clear(); Main.LastVotedPlayerInfo = null; diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index 07234db0b..905e4c36e 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -1134,7 +1134,7 @@ internal static class StartGameHostPatch if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out (RoleTypes, CustomRoles) roleMap)) { - int targetClientId = target.GetClientId(); + int targetClientId = target.OwnerId; if (targetClientId == -1) continue; RoleTypes roleType = roleMap.Item1; @@ -1155,7 +1155,7 @@ internal static class StartGameHostPatch { if (target == null) return; - int targetClientId = target.GetClientId(); + int targetClientId = target.OwnerId; if (targetClientId == -1) return; RoleTypes roleType = RpcSetRoleReplacer.RoleMap.TryGetValue((target.PlayerId, target.PlayerId), out (RoleTypes RoleType, CustomRoles CustomRole) roleMap) @@ -1394,7 +1394,7 @@ internal static class StartGameHostPatch PlayerControl seer = Utils.GetPlayerById(seerId); if (seer == null || target == null) continue; - int targetClientId = target.GetClientId(); + int targetClientId = target.OwnerId; if (targetClientId == -1) continue; sender.RpcSetRole(seer, roleType, targetClientId, false); diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index 371a112a4..16ceb2940 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -98,7 +98,7 @@ public static class PhantomRolePatch { if (!target.IsAlive() || phantom.PlayerId == target.PlayerId || target.AmOwner || !target.HasDesyncRole()) continue; - int clientId = target.GetClientId(); + int clientId = target.OwnerId; if (AmongUsClient.Instance.ClientId == clientId) { @@ -128,7 +128,7 @@ public static class PhantomRolePatch { if (GameStates.IsMeeting || phantom == null || target.PlayerId == phantom.PlayerId) return; - int clientId = target.GetClientId(); + int clientId = target.OwnerId; string petId = phantom.Data.DefaultOutfit.PetId; if (petId != "") @@ -211,7 +211,7 @@ public static class PhantomRolePatch { if (target == null || target.PlayerId == phantom.PlayerId) continue; - int clientId = target.GetClientId(); + int clientId = target.OwnerId; if (AmongUsClient.Instance.ClientId == clientId) phantom.CheckAppear(shouldAnimate); @@ -236,7 +236,7 @@ public static class PhantomRolePatch { if (GameStates.IsMeeting || phantom == null || target.PlayerId == phantom.PlayerId) return; - int clientId = target.GetClientId(); + int clientId = target.OwnerId; InvisibilityList.Remove(phantom); sender.RpcSetRole(phantom, RoleTypes.Scientist, clientId); @@ -292,14 +292,14 @@ public static class PhantomRolePatch // Set Scientist for meeting if (!force) yield return new WaitForSeconds(0.0001f); - if (seer.GetClientId() == -1 || phantom == null) yield break; + if (seer.OwnerId == -1 || phantom == null) yield break; phantom.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); // Return Phantom in meeting yield return new WaitForSeconds(1f); { - if (seer.GetClientId() == -1 || phantom == null) yield break; + if (seer.OwnerId == -1 || phantom == null) yield break; phantom.RpcSetRoleDesync(RoleTypes.Phantom, seer.GetClientId()); } @@ -307,7 +307,7 @@ public static class PhantomRolePatch yield return new WaitForSeconds(1f); { - if (seer.GetClientId() == -1 || phantom == null) yield break; + if (seer.OwnerId == -1 || phantom == null) yield break; phantom.RpcStartAppearDesync(false, seer); } @@ -315,7 +315,7 @@ public static class PhantomRolePatch yield return new WaitForSeconds(4f); { - if (seer.GetClientId() == -1 || phantom == null) yield break; + if (seer.OwnerId == -1 || phantom == null) yield break; phantom.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); if (PetsList.TryGetValue(phantom.PlayerId, out string petId)) diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 6b021ec22..b93d2aef5 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -1429,7 +1429,7 @@ internal static class FixedUpdatePatch { string msg = string.Format(GetString("KickBecauseLowLevel"), player.GetRealName().RemoveHtmlTags()); Logger.SendInGame(msg); - AmongUsClient.Instance.KickPlayer(player.GetClientId(), true); + AmongUsClient.Instance.KickPlayer(player.OwnerId, true); Logger.Info(msg, "Low Level Temp Ban"); } } @@ -1470,7 +1470,7 @@ internal static class FixedUpdatePatch PlagueBearer.PlayerIdList.Remove(playerId); } - bool checkPos = inTask && player != null && alive && !Pelican.IsEaten(playerId); + bool checkPos = inTask && !ExileController.Instance && !AntiBlackout.SkipTasks && player != null && alive && !Pelican.IsEaten(playerId) && Main.IntroDestroyed; if (checkPos) Asthmatic.OnCheckPlayerPosition(player); foreach (PlayerState state in Main.PlayerStates.Values) @@ -1478,7 +1478,6 @@ internal static class FixedUpdatePatch if (state.Role.IsEnable) { if (checkPos) state.Role.OnCheckPlayerPosition(player); - state.Role.OnGlobalFixedUpdate(player, lowLoad); } } @@ -1952,12 +1951,14 @@ internal static class FixedUpdatePatch } } -[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Start))] +[HarmonyPatch(typeof(PlayerControl._Start_d__82), nameof(PlayerControl._Start_d__82.MoveNext))] internal static class PlayerStartPatch { - public static void Postfix(PlayerControl __instance) + public static void Postfix(PlayerControl._Start_d__82 __instance, ref bool __result) { - TextMeshPro roleText = Object.Instantiate(__instance.cosmetics.nameText, __instance.cosmetics.nameText.transform, true); + if (__result) return; + TextMeshPro nameText = __instance.__4__this.cosmetics.nameText; + TextMeshPro roleText = Object.Instantiate(nameText, nameText.transform, true); roleText.transform.localPosition = new(0f, 0.2f, 0f); roleText.fontSize -= 0.9f; roleText.text = "RoleText"; @@ -1966,17 +1967,6 @@ internal static class PlayerStartPatch } } -//[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))] -//class SetColorPatch -//{ -// public static bool IsAntiGlitchDisabled; - -// public static bool Prefix(PlayerControl __instance, int bodyColor) -// { -// return true; -// } -//} - [HarmonyPatch(typeof(Vent), nameof(Vent.ExitVent))] internal static class ExitVentPatch { diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 663599680..f2e2d2501 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -279,7 +279,6 @@ internal static class OnPlayerLeftPatch NameNotifyManager.Notifies.Remove(data.Character.PlayerId); data.Character.RpcSetName(data.Character.GetRealName(true)); - AntiBlackout.OnDisconnect(data.Character.Data); PlayerGameOptionsSender.RemoveSender(data.Character); } diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index e5a630e5a..70d228d49 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -382,6 +382,8 @@ public static class SabotageSystemTypeRepairDamagePatch player.Notify(BlockedAction.Sabotage.GetBlockNotify()); return false; } + + if (Pelican.IsEaten(player.PlayerId)) return false; if (!Rhapsode.CheckAbilityUse(player) || Stasis.IsTimeFrozen || TimeMaster.Rewinding) return false; diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index d40800263..e680b46f6 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -68,6 +68,7 @@ public class PlagueDoctor : RoleBase { PlayerIdList = []; InfectInfos = []; + InfectActive = false; } public override void Add(byte playerId) @@ -82,11 +83,11 @@ public class PlagueDoctor : RoleBase playerId.SetAbilityUseLimit(InfectLimit); - InfectActive = true; - if (Main.NormalOptions.MapId == 4) // Fixed airship respawn selection delay InfectInactiveTime += 5f; + + LateTask.New(() => InfectActive = true, 10f + InfectInactiveTime, log: false); PlayerIdList.Add(playerId); } @@ -183,9 +184,7 @@ public class PlagueDoctor : RoleBase public override void OnCheckPlayerPosition(PlayerControl player) { if (!IsEnable) return; - if (!AmongUsClient.Instance.AmHost) return; - if (!GameStates.IsInTask) return; if (LateCheckWin) @@ -248,11 +247,10 @@ public class PlagueDoctor : RoleBase LateCheckWin = true; LateTask.New(() => - { - Logger.Info("Infect Active", "PlagueDoctor"); - InfectActive = true; - }, - InfectInactiveTime, "ResetInfectInactiveTime"); + { + Logger.Info("Infect Active", "PlagueDoctor"); + InfectActive = true; + }, InfectInactiveTime, "ResetInfectInactiveTime"); } public static string GetMarkOthers(PlayerControl seer, PlayerControl seen = null) @@ -260,22 +258,14 @@ public class PlagueDoctor : RoleBase if (Main.PlayerStates[seer.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return string.Empty; seen ??= seer; - if (!pd.CanInfect(seen)) return string.Empty; - - if (!seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive()) return string.Empty; + if (!pd.CanInfect(seen) || !seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive()) return string.Empty; return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PlagueDoctor), GetInfectRateCharactor(seen, pd)); } public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) { - if (seer.PlayerId != target.PlayerId && seer.IsAlive()) return string.Empty; - - if (!seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive()) return string.Empty; - - if (!hud && seer.IsModdedClient()) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return string.Empty; + if (seer.PlayerId != target.PlayerId && seer.IsAlive() || !seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive() || !hud && seer.IsModdedClient() || Main.PlayerStates[seer.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return string.Empty; var str = new StringBuilder(40); @@ -294,13 +284,9 @@ public class PlagueDoctor : RoleBase return rate >= 100; } - public static string GetInfectRateCharactor(PlayerControl player, PlagueDoctor pd) + private static string GetInfectRateCharactor(PlayerControl player, PlagueDoctor pd) { - if (!pd.IsEnable) return string.Empty; - - if (!pd.CanInfect(player) || !player.IsAlive()) return string.Empty; - - if (!InfectInfos.TryGetValue(player.PlayerId, out float rate)) return string.Empty; + if (!pd.IsEnable || !pd.CanInfect(player) || !player.IsAlive() || !InfectInfos.TryGetValue(player.PlayerId, out float rate)) return string.Empty; return rate switch { @@ -326,8 +312,8 @@ public class PlagueDoctor : RoleBase private void CheckWin() { if (!IsEnable) return; - if (!AmongUsClient.Instance.AmHost) return; + if (!Main.IntroDestroyed) return; // Invalid if someone's victory is being processed if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; @@ -341,12 +327,16 @@ public class PlagueDoctor : RoleBase foreach (PlayerControl player in Main.AllAlivePlayerControls) { if (player.Is(CustomRoles.PlagueDoctor)) continue; - player.Suicide(PlayerState.DeathReason.Curse, pd); } CustomWinnerHolder.ResetAndSetWinner(CustomWinner.PlagueDoctor); - foreach (PlayerControl plagueDoctor in Main.AllPlayerControls.Where(p => p.Is(CustomRoles.PlagueDoctor)).ToArray()) CustomWinnerHolder.WinnerIds.Add(plagueDoctor.PlayerId); + + foreach (PlayerControl plagueDoctor in Main.AllPlayerControls) + { + if (plagueDoctor.Is(CustomRoles.PlagueDoctor)) + CustomWinnerHolder.WinnerIds.Add(plagueDoctor.PlayerId); + } } }