diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index 781dfcb39..ed3919ce7 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -40,8 +40,6 @@ public static class AntiBlackout return; } - revived.Data.IsDead = false; - revived.Data.SendGameData(); revived.RpcChangeRoleBasis(CustomRoles.CrewmateEHR, forced: true); } @@ -59,10 +57,12 @@ public static class AntiBlackout { try { - if (pc.Data != null && !pc.Data.IsDead && !pc.Data.Disconnected && !pc.IsAlive()) + NetworkedPlayerInfo data = pc.Data; + + if (data != null && !data.IsDead && !data.Disconnected && !pc.IsAlive()) { - pc.Data.IsDead = true; - pc.Data.SendGameData(); + data.IsDead = true; + data.SendGameData(); } } catch (Exception e) { Utils.ThrowException(e); } @@ -103,7 +103,7 @@ public static class AntiBlackout { // 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); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(pc.NetId, 4, SendOption.Reliable); AmongUsClient.Instance.FinishRpcImmediately(writer); if (pc.HasDesyncRole()) pc.FixBlackScreen(); if (pc.HasGhostRole()) pc.RpcResetAbilityCooldown(); diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index 4353eeec6..428368a2e 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -85,7 +85,12 @@ public static class Camouflage { if (Camouflager.On && Camouflager.IsActive) return true; - if (Main.CurrentMap == MapNames.Fungle && Options.CommsCamouflageDisableOnFungle.GetBool()) return false; + switch (Main.CurrentMap) + { + case MapNames.Fungle when Options.CommsCamouflageDisableOnFungle.GetBool(): + case MapNames.MiraHQ when Options.CommsCamouflageDisableOnMira.GetBool(): + return false; + } if (Utils.IsActive(SystemTypes.Comms) && Options.CommsCamouflage.GetBool()) { diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 82f39a462..027ffe13a 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -77,7 +77,14 @@ internal static class ExtendedPlayerControl public static bool CanUseVent(this PlayerControl player, int ventId) { - if (Options.CurrentGameMode == CustomGameMode.RoomRush) return true; + switch (Options.CurrentGameMode) + { + case CustomGameMode.RoomRush: + return true; + case CustomGameMode.Standard when Main.AllAlivePlayerControls.Length == 2: + return false; + } + if (player.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) return false; if (player.Is(CustomRoles.Blocked) && player.GetClosestVent()?.Id != ventId) return false; if (!GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks) return false; @@ -325,11 +332,11 @@ 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, bool forced = false) + public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false, bool forced = false) { if (!forced) { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null || !player.IsAlive()) return false; + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null || !player.IsAlive()) return; if (AntiBlackout.SkipTasks || ExileController.Instance) { @@ -339,7 +346,7 @@ internal static class ExtendedPlayerControl 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; + return; IEnumerator DelayBasisChange() { @@ -521,8 +528,6 @@ internal static class ExtendedPlayerControl 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; - - return true; } // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/Utils.cs diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 26f421732..375ad2425 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -24,13 +24,13 @@ public static class GuessManager private static readonly int Mask = Shader.PropertyToID("_Mask"); public static HashSet Guessers = []; private static int Page; - private static GameObject guesserUI; + private static GameObject GuesserUI; private static Dictionary> RoleButtons; private static Dictionary RoleSelectButtons; private static List PageButtons; - private static CustomRoleTypes currentTeamType; + private static CustomRoleTypes CurrentTeamType; - public static TextMeshPro textTemplate; + public static TextMeshPro TextTemplate; public static string GetFormatString() { @@ -84,7 +84,7 @@ public static class GuessManager { case 1: Utils.SendMessage(GetFormatString(), pc.PlayerId); - return true; + break; case 2: { if (!pc.IsAlive()) @@ -310,6 +310,7 @@ public static class GuessManager case CustomRoles.Ankylosaurus: ShowMessage("GuessAnkylosaurus"); return true; + case CustomRoles.Speedrunner when target.Is(CustomRoles.Speedrunner) && !pc.Is(Team.Crewmate) && target.GetTaskState().CompletedTasksCount >= Speedrunner.SpeedrunnerNotifyAtXTasksLeft.GetInt() && Speedrunner.SpeedrunnerNotifyKillers.GetBool(): case CustomRoles.DonutDelivery when DonutDelivery.IsUnguessable(pc, target): case CustomRoles.Shifter: case CustomRoles.Car: @@ -713,45 +714,39 @@ public static class GuessManager private static void GuesserSelectRole(CustomRoleTypes role, bool setPage = true) { - currentTeamType = role; + CurrentTeamType = role; if (setPage) Page = 1; - foreach (KeyValuePair> RoleButton in RoleButtons) + foreach (KeyValuePair> roleButton in RoleButtons) { var index = 0; - foreach (Transform RoleBtn in RoleButton.Value) + foreach (Transform roleBtn in roleButton.Value) { - if (RoleBtn == null) continue; + if (roleBtn == null) continue; index++; - if (index <= (Page - 1) * 40) + if (index <= (Page - 1) * 40 || Page * 40 < index) { - RoleBtn.gameObject.SetActive(false); + roleBtn.gameObject.SetActive(false); continue; } - if (Page * 40 < index) - { - RoleBtn.gameObject.SetActive(false); - continue; - } - - RoleBtn.gameObject.SetActive(RoleButton.Key == role); + roleBtn.gameObject.SetActive(roleButton.Key == role); } } - foreach (KeyValuePair RoleButton in RoleSelectButtons) + foreach (KeyValuePair roleButton in RoleSelectButtons) { - if (RoleButton.Value == null) continue; - RoleButton.Value.color = new(0, 0, 0, RoleButton.Key == role ? 1 : 0.25f); + if (roleButton.Value == null) continue; + roleButton.Value.color = new(0, 0, 0, roleButton.Key == role ? 1 : 0.25f); } } private static void GuesserOnClick(byte playerId, MeetingHud __instance) { PlayerControl pc = Utils.GetPlayerById(playerId); - if (pc == null || !pc.IsAlive() || guesserUI != null || !GameStates.IsVoting) return; + if (pc == null || !pc.IsAlive() || GuesserUI != null || !GameStates.IsVoting) return; try { @@ -763,14 +758,14 @@ public static class GuessManager Transform container = Object.Instantiate(GameObject.Find("PhoneUI").transform, __instance.transform); container.transform.localPosition = new Vector3(0, 0, -200f); - guesserUI = container.gameObject; + GuesserUI = container.gameObject; List i = [0, 0, 0, 0, 0]; Transform buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase"); Transform maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); Transform smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); - textTemplate.enabled = true; - if (textTemplate.transform.FindChild("RoleTextMeeting") != null) Object.Destroy(textTemplate.transform.FindChild("RoleTextMeeting").gameObject); + TextTemplate.enabled = true; + if (TextTemplate.transform.FindChild("RoleTextMeeting") != null) Object.Destroy(TextTemplate.transform.FindChild("RoleTextMeeting").gameObject); Transform exitButtonParent = new GameObject().transform; exitButtonParent.SetParent(container); @@ -809,19 +804,19 @@ public static class GuessManager continue; } - Transform TeambuttonParent = new GameObject().transform; - TeambuttonParent.SetParent(container); - Transform Teambutton = Object.Instantiate(buttonTemplate, TeambuttonParent); - Teambutton.FindChild("ControllerHighlight").gameObject.SetActive(false); - Object.Instantiate(maskTemplate, TeambuttonParent); - TextMeshPro Teamlabel = Object.Instantiate(textTemplate, Teambutton); - var spriteRenderer = Teambutton.GetComponent(); + Transform teambuttonParent = new GameObject().transform; + teambuttonParent.SetParent(container); + Transform teambutton = Object.Instantiate(buttonTemplate, teambuttonParent); + teambutton.FindChild("ControllerHighlight").gameObject.SetActive(false); + Object.Instantiate(maskTemplate, teambuttonParent); + TextMeshPro teamlabel = Object.Instantiate(TextTemplate, teambutton); + var spriteRenderer = teambutton.GetComponent(); spriteRenderer.sprite = CustomButton.Get("GuessPlate"); RoleSelectButtons.Add((CustomRoleTypes)index, spriteRenderer); - TeambuttonParent.localPosition = new(-3.10f + (tabCount++ * 1.47f), 2.225f, -200); - TeambuttonParent.localScale = new(0.53f, 0.53f, 1f); + teambuttonParent.localPosition = new(-3.10f + (tabCount++ * 1.47f), 2.225f, -200); + teambuttonParent.localScale = new(0.53f, 0.53f, 1f); - Teamlabel.color = (CustomRoleTypes)index switch + teamlabel.color = (CustomRoleTypes)index switch { CustomRoleTypes.Coven => Team.Coven.GetColor(), CustomRoleTypes.Crewmate => new Color32(140, 255, 255, byte.MaxValue), @@ -831,14 +826,14 @@ public static class GuessManager _ => throw new ArgumentOutOfRangeException("The index is out of range, it's an invalid CustomRoleTypes (GuessManager.cs:GuesserOnClick method)", innerException: null) }; - Logger.Info(Teamlabel.color.ToString(), ((CustomRoleTypes)index).ToString()); - Teamlabel.text = GetString("Type" + (CustomRoleTypes)index); - Teamlabel.alignment = TextAlignmentOptions.Center; - Teamlabel.transform.localPosition = new Vector3(0, 0, Teamlabel.transform.localPosition.z); - Teamlabel.transform.localScale *= 1.6f; - Teamlabel.autoSizeTextContainer = true; + Logger.Info(teamlabel.color.ToString(), ((CustomRoleTypes)index).ToString()); + teamlabel.text = GetString("Type" + (CustomRoleTypes)index); + teamlabel.alignment = TextAlignmentOptions.Center; + teamlabel.transform.localPosition = new Vector3(0, 0, teamlabel.transform.localPosition.z); + teamlabel.transform.localScale *= 1.6f; + teamlabel.autoSizeTextContainer = true; - if (PlayerControl.LocalPlayer.IsAlive()) CreateTeamButton(Teambutton, (CustomRoleTypes)index); + if (PlayerControl.LocalPlayer.IsAlive()) CreateTeamButton(teambutton, (CustomRoleTypes)index); continue; static void CreateTeamButton(Transform teambutton, CustomRoleTypes type) => @@ -854,12 +849,12 @@ public static class GuessManager PageButtons[0].color = new(1, 1, 1, 1f); PageButtons[1].color = new(1, 1, 1, 1f); - if ((RoleButtons[currentTeamType].Count / MaxOneScreenRole) + (RoleButtons[currentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page) + if ((RoleButtons[CurrentTeamType].Count / MaxOneScreenRole) + (RoleButtons[CurrentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page) { Page -= 1; PageButtons[1].color = new(1, 1, 1, 0.1f); } - else if ((RoleButtons[currentTeamType].Count / MaxOneScreenRole) + (RoleButtons[currentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page + 1) + else if ((RoleButtons[CurrentTeamType].Count / MaxOneScreenRole) + (RoleButtons[CurrentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page + 1) PageButtons[1].color = new(1, 1, 1, 0.1f); if (Page <= 1) @@ -868,31 +863,31 @@ public static class GuessManager PageButtons[0].color = new(1, 1, 1, 0.1f); } - GuesserSelectRole(currentTeamType, false); + GuesserSelectRole(CurrentTeamType, false); } static void CreatePage(bool isNext, MeetingHud __instance, Transform container) { Transform buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase"); Transform maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); - Transform PagebuttonParent = new GameObject().transform; - PagebuttonParent.SetParent(container); - Transform Pagebutton = Object.Instantiate(buttonTemplate, PagebuttonParent); - Pagebutton.FindChild("ControllerHighlight").gameObject.SetActive(false); - Object.Instantiate(maskTemplate, PagebuttonParent); - TextMeshPro Pagelabel = Object.Instantiate(textTemplate, Pagebutton); - Pagebutton.GetComponent().sprite = CustomButton.Get("GuessPlateKPD"); - PagebuttonParent.localPosition = isNext ? new(3.535f, -2.2f, -200) : new(-3.475f, -2.2f, -200); - PagebuttonParent.localScale = new(0.55f, 0.55f, 1f); - Pagelabel.color = Color.white; - Pagelabel.text = GetString(isNext ? "NextPage" : "PreviousPage"); - Pagelabel.alignment = TextAlignmentOptions.Center; - Pagelabel.transform.localPosition = new Vector3(0, 0, Pagelabel.transform.localPosition.z); - Pagelabel.transform.localScale *= 1.6f; - Pagelabel.autoSizeTextContainer = true; - if (!isNext && Page <= 1) Pagebutton.GetComponent().color = new(1, 1, 1, 0.1f); - Pagebutton.GetComponent().OnClick.AddListener((Action)ClickEvent); - PageButtons.Add(Pagebutton.GetComponent()); + Transform pagebuttonParent = new GameObject().transform; + pagebuttonParent.SetParent(container); + Transform pagebutton = Object.Instantiate(buttonTemplate, pagebuttonParent); + pagebutton.FindChild("ControllerHighlight").gameObject.SetActive(false); + Object.Instantiate(maskTemplate, pagebuttonParent); + TextMeshPro pagelabel = Object.Instantiate(TextTemplate, pagebutton); + pagebutton.GetComponent().sprite = CustomButton.Get("GuessPlateKPD"); + pagebuttonParent.localPosition = isNext ? new(3.535f, -2.2f, -200) : new(-3.475f, -2.2f, -200); + pagebuttonParent.localScale = new(0.55f, 0.55f, 1f); + pagelabel.color = Color.white; + pagelabel.text = GetString(isNext ? "NextPage" : "PreviousPage"); + pagelabel.alignment = TextAlignmentOptions.Center; + pagelabel.transform.localPosition = new Vector3(0, 0, pagelabel.transform.localPosition.z); + pagelabel.transform.localScale *= 1.6f; + pagelabel.autoSizeTextContainer = true; + if (!isNext && Page <= 1) pagebutton.GetComponent().color = new(1, 1, 1, 0.1f); + pagebutton.GetComponent().OnClick.AddListener((Action)ClickEvent); + PageButtons.Add(pagebutton.GetComponent()); return; void ClickEvent() @@ -943,13 +938,14 @@ public static class GuessManager void CreateRole(CustomRoles role) { - if (40 <= i[(int)role.GetCustomRoleTypes()]) i[(int)role.GetCustomRoleTypes()] = 0; + var customRoleTypesInt = (int)role.GetCustomRoleTypes(); + if (40 <= i[customRoleTypesInt]) i[customRoleTypesInt] = 0; Transform buttonParent = new GameObject().transform; buttonParent.SetParent(container); Transform button = Object.Instantiate(buttonTemplate, buttonParent); button.FindChild("ControllerHighlight").gameObject.SetActive(false); Object.Instantiate(maskTemplate, buttonParent); - TextMeshPro label = Object.Instantiate(textTemplate, button); + TextMeshPro label = Object.Instantiate(TextTemplate, button); button.GetComponent().sprite = CustomButton.Get("GuessPlate"); @@ -958,8 +954,8 @@ public static class GuessManager RoleButtons[role.GetCustomRoleTypes()].Add(button); buttons.Add(button); - int row = i[(int)role.GetCustomRoleTypes()] / 5; - int col = i[(int)role.GetCustomRoleTypes()] % 5; + int row = i[customRoleTypesInt] / 5; + int col = i[customRoleTypesInt] % 5; buttonParent.localPosition = new Vector3(-3.47f + (1.75f * col), 1.5f - (0.45f * row), -200f); buttonParent.localScale = new Vector3(0.55f, 0.55f, 1f); label.text = GetString(role.ToString()); @@ -982,7 +978,7 @@ public static class GuessManager } else { - if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || !PlayerControl.LocalPlayer.IsAlive()) return; + if (__instance.state is not (MeetingHud.VoteStates.Voted or MeetingHud.VoteStates.NotVoted) || !PlayerControl.LocalPlayer.IsAlive()) return; Logger.Msg($"Click: {pc.GetNameWithRole()} => {role}", "Guesser UI"); @@ -992,12 +988,12 @@ public static class GuessManager // Reset the GUI __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); Object.Destroy(container.gameObject); - textTemplate.enabled = false; + TextTemplate.enabled = false; } })); } - i[(int)role.GetCustomRoleTypes()]++; + i[customRoleTypesInt]++; } container.transform.localScale *= 0.75f; @@ -1024,17 +1020,18 @@ public static class GuessManager public static void ReceiveRPC(MessageReader reader, PlayerControl pc) { - Logger.Msg($"{reader}", "GuessManager - MessageReader reader"); - Logger.Msg($"{pc}", "GuessManager - PlayerControl pc"); + Logger.Msg($"{pc.GetNameWithRole()}", "GuessManager - PlayerControl pc"); - int PlayerId = reader.ReadInt32(); - Logger.Msg($"{PlayerId}", "GuessManager - Player Id"); + int playerId = reader.ReadInt32(); + Logger.Msg($"{playerId}", "GuessManager - Player Id"); var role = (CustomRoles)reader.ReadInt32(); Logger.Msg($"{role}", "GuessManager - Role Int32"); - Logger.Msg($"{GetString(role.ToString())}", "GuessManager - Role String"); - GuesserMsg(pc, $"/bt {PlayerId} {GetString(role.ToString())}", true); + string roleStr = GetString(role.ToString()); + Logger.Msg($"{roleStr}", "GuessManager - Role String"); + + GuesserMsg(pc, $"/bt {playerId} {roleStr}", true); } [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] @@ -1056,7 +1053,19 @@ public static class GuessManager PlayerControl lp = PlayerControl.LocalPlayer; if (!lp.IsAlive()) return; - bool canGuess = lp.Is(CustomRoles.Guesser) || lp.GetCustomRole() switch + if (CanGuess(lp, restrictions)) + CreateGuesserButton(__instance); + + if (AmongUsClient.Instance.AmHost) + { + HashSet guessers = Main.AllPlayerControls.Where(x => CanGuess(x, false)).Select(x => x.PlayerId).ToHashSet(); + LateTask.New(() => guessers.Do(x => Utils.SendMessage("\n", x, GetString("YouCanGuess"))), 12f, log: false); + } + } + + private static bool CanGuess(PlayerControl lp, bool restrictions) + { + return lp.Is(CustomRoles.Guesser) || lp.GetCustomRole() switch { CustomRoles.EvilGuesser => true, CustomRoles.NiceGuesser => true, @@ -1075,8 +1084,6 @@ public static class GuessManager } && !(restrictions && !Guessers.Contains(lp.PlayerId)), _ => false }; - - if (canGuess) CreateGuesserButton(__instance); } private static void InitializeGuesserPlayers() @@ -1100,7 +1107,7 @@ public static class GuessManager [SuppressMessage("ReSharper", "UnusedMember.Local")] public static void Postfix() { - Object.Destroy(textTemplate.gameObject); + Object.Destroy(TextTemplate.gameObject); } } } \ No newline at end of file diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index 5eab98b78..fcef3f33d 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -582,6 +582,7 @@ public static class Options // Sabotage public static OptionItem CommsCamouflage; public static OptionItem CommsCamouflageDisableOnFungle; + public static OptionItem CommsCamouflageDisableOnMira; public static OptionItem CommsCamouflageLimit; public static OptionItem CommsCamouflageLimitSetChance; public static OptionItem CommsCamouflageLimitChance; @@ -711,6 +712,7 @@ public static class Options public static OptionItem StoreCompletedAchievementsOnEHRDatabase; public static OptionItem AllCrewRolesHaveVanillaColor; public static OptionItem MessageRpcSizeLimit; + public static OptionItem KickSlowJoiningPlayers; public static OptionItem DraftMaxRolesPerPlayer; public static OptionItem LargerRoleTextSize; public static OptionItem ShowTaskCountWhenAlive; @@ -1582,6 +1584,9 @@ public static class Options MessageRpcSizeLimit = new IntegerOptionItem(19425, "MessageRpcSizeLimit", new(500, 100000, 100), 1400, TabGroup.SystemSettings) .SetHeader(true); + KickSlowJoiningPlayers = new BooleanOptionItem(19428, "KickSlowJoiningPlayers", false, TabGroup.SystemSettings) + .SetHeader(true); + RoleAssigningAlgorithm = new StringOptionItem(19409, "RoleAssigningAlgorithm", RoleAssigningAlgorithms, 4, TabGroup.SystemSettings, true) .SetHeader(true) .RegisterUpdateValueEvent((_, args) => IRandom.SetInstanceById(args.CurrentValue)); @@ -1833,6 +1838,11 @@ public static class Options .SetParent(CommsCamouflage) .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + CommsCamouflageDisableOnMira = new BooleanOptionItem(22201, "CommsCamouflageDisableOnMira", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflage) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + CommsCamouflageLimit = new BooleanOptionItem(22203, "CommsCamouflageLimit", false, TabGroup.GameSettings) .SetGameMode(CustomGameMode.Standard) .SetParent(CommsCamouflage) diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index f06f22f58..f8fa22797 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -198,7 +198,7 @@ internal static class ChatCommands new(["gm", "gml", "gamemodes", "gamemodelist", "режимы", "模式列表"], "", GetString("CommandDescription.GameModeList"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, GameModeListCommand, true, false), new(["gmp", "gmpoll", "pollgm", "gamemodepoll", "режимголосование", "模式投票"], "", GetString("CommandDescription.GameModePoll"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.InLobby, GameModePollCommand, true, false), new(["8ball", "шар", "八球"], "[question]", GetString("CommandDescription.EightBall"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, EightBallCommand, false, false, [GetString("CommandArgs.EightBall.Question")]), - new(["addtag", "добавитьтег", "添加标签", "adicionartag"], "{id} {color} {tag}", GetString("CommandDescription.AddTag"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, AddTagCommand, true, false, [GetString("CommandArgs.AddTag.Id"), GetString("CommandArgs.AddTag.Color"), GetString("CommandArgs.AddTag.Tag")]), + new(["addtag", "createtag", "добавитьтег", "添加标签", "adicionartag"], "{id} {color} {tag}", GetString("CommandDescription.AddTag"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, AddTagCommand, true, false, [GetString("CommandArgs.AddTag.Id"), GetString("CommandArgs.AddTag.Color"), GetString("CommandArgs.AddTag.Tag")]), new(["deletetag", "удалитьтег", "删除标签"], "{id}", GetString("CommandDescription.DeleteTag"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, DeleteTagCommand, true, false, [GetString("CommandArgs.DeleteTag.Id")]), new(["daybreak", "db", "дейбрейк", "破晓"], "", GetString("CommandDescription.DayBreak"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DayBreakCommand, true, true), new(["fix", "blackscreenfix", "fixblackscreen", "ф", "исправить", "修复"], "{id}", GetString("CommandDescription.Fix"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.InGame, FixCommand, true, false, [GetString("CommandArgs.Fix.Id")]), diff --git a/Patches/EndGameManagerPatch.cs b/Patches/EndGameManagerPatch.cs index d74576220..a9036849c 100644 --- a/Patches/EndGameManagerPatch.cs +++ b/Patches/EndGameManagerPatch.cs @@ -71,7 +71,7 @@ internal static class EndGameNavigationNextGamePatch { public static void Postfix() { - if (!AmongUsClient.Instance.AmHost) return; + if (!AmongUsClient.Instance.AmHost || !Options.KickSlowJoiningPlayers.GetBool()) return; LateTask.New(() => { diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index cf58fbea2..f7f3fc322 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -1030,8 +1030,14 @@ internal static class IntroCutsceneDestroyPatch LateTask.New(() => { lp.RpcChangeRoleBasis(lp.GetCustomRole()); - lp.RpcResetAbilityCooldown(); - LateTask.New(() => lp.SetKillCooldown(), 0.2f, log: false); + + LateTask.New(() => + { + lp.RpcResetAbilityCooldown(); + lp.SetKillCooldown(); + }, 0.2f, log: false); + + StartGameHostPatch.RpcSetRoleReplacer.SetActualSelfRolesAfterOverride(); }, 0.1f, log: false); if (Options.UsePets.GetBool() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek or CustomGameMode.CaptureTheFlag) diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 1b026f317..b9b83b14f 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -824,8 +824,8 @@ internal static class MeetingHudStartPatch { SoundManager.Instance.ChangeAmbienceVolume(0f); - GuessManager.textTemplate = Object.Instantiate(__instance.playerStates[0].NameText); - GuessManager.textTemplate.enabled = false; + GuessManager.TextTemplate = Object.Instantiate(__instance.playerStates[0].NameText); + GuessManager.TextTemplate.enabled = false; PlayerControl seer = PlayerControl.LocalPlayer; diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index e7a2c58dd..6a803ad26 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -1157,7 +1157,10 @@ internal static class StartGameHostPatch ? roleMap.RoleType : RpcSetRoleReplacer.StoragedData[target.PlayerId]; - target.RpcSetRoleDesync(roleType, targetClientId); + bool forceDisplayCrewmate = target.Is(Team.Crewmate) && roleType is not (RoleTypes.Crewmate or RoleTypes.Scientist or RoleTypes.Engineer or RoleTypes.Noisemaker or RoleTypes.Tracker or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel); + if (forceDisplayCrewmate) RpcSetRoleReplacer.OverriddenTeamRevealScreen[target.PlayerId] = roleType; + + target.RpcSetRoleDesync(forceDisplayCrewmate ? RoleTypes.Crewmate : roleType, targetClientId); } catch (Exception e) { Utils.ThrowException(e); } } @@ -1217,6 +1220,7 @@ internal static class StartGameHostPatch public static Dictionary StoragedData = []; public static Dictionary<(byte SeerID, byte TargetID), (RoleTypes RoleType, CustomRoles CustomRole)> RoleMap = []; public static List OverriddenSenderList = []; + public static Dictionary OverriddenTeamRevealScreen = []; public static void Initialize() { @@ -1225,6 +1229,7 @@ internal static class StartGameHostPatch RoleMap = []; StoragedData = []; OverriddenSenderList = []; + OverriddenTeamRevealScreen = []; } // ReSharper disable once MemberHidesStaticFromOuterClass @@ -1426,5 +1431,27 @@ internal static class StartGameHostPatch } catch (Exception e) { Utils.ThrowException(e); } } + + public static void SetActualSelfRolesAfterOverride() + { + foreach ((byte id, RoleTypes roleTypes) in OverriddenTeamRevealScreen) + { + PlayerControl pc = id.GetPlayer(); + if (pc == null || !pc.IsAlive()) continue; + + int targetClientId = pc.OwnerId; + if (targetClientId == -1) continue; + + pc.RpcSetRoleDesync(roleTypes, targetClientId); + + LateTask.New(() => + { + pc.RpcResetAbilityCooldown(); + pc.SetKillCooldown(); + }, 0.2f, log: false); + } + + OverriddenTeamRevealScreen = null; + } } } \ No newline at end of file diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 640ec73d0..fdd50d7c8 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -882,7 +882,9 @@ internal static class ShapeshiftPatch foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (pc.Is(CustomRoles.Shiftguard)) pc.Notify(shapeshifting ? GetString("ShiftguardNotifySS") : GetString("ShiftguardNotifyUnshift")); + PlainShipRoom plainShipRoom = pc.GetPlainShipRoom(); + string room = GetString(plainShipRoom != null ? plainShipRoom.RoomId.ToString() : "Outside"); + if (pc.Is(CustomRoles.Shiftguard)) pc.Notify($"[<#00ffa5>{room}] {GetString(shapeshifting ? "ShiftguardNotifySS" : "ShiftguardNotifyUnshift")}"); switch (Main.PlayerStates[pc.PlayerId].Role) { diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index cf07712ef..8107a03f2 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -210,25 +210,24 @@ internal static class OnPlayerJoinedPatch { try { - if (AmongUsClient.Instance.AmHost) + if (!AmongUsClient.Instance.AmHost) return; + + if (Options.KickSlowJoiningPlayers.GetBool() && ((!client.IsDisconnected() && client.Character.Data.IsIncomplete) || ((client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length <= 15))) { - if ((!client.IsDisconnected() && client.Character.Data.IsIncomplete) || ((client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length <= 15)) - { - Logger.SendInGame(GetString("Error.InvalidColor") + $" {client.Id}/{client.PlayerName}"); - AmongUsClient.Instance.KickPlayer(client.Id, false); - Logger.Info($"Kicked client {client.Id}/{client.PlayerName} since its PlayerControl was not spawned in time.", "OnPlayerJoinedPatchPostfix"); - return; - } - - if (!Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) - { - MessageWriter retry = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.None, client.Id); - AmongUsClient.Instance.FinishRpcImmediately(retry); - } - - if (client.Character != null && client.Character.Data != null && (client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length >= 17) - Disco.ChangeColor(client.Character); + Logger.SendInGame(GetString("Error.InvalidColor") + $" {client.Id}/{client.PlayerName}"); + AmongUsClient.Instance.KickPlayer(client.Id, false); + Logger.Info($"Kicked client {client.Id}/{client.PlayerName} since its PlayerControl was not spawned in time.", "OnPlayerJoinedPatchPostfix"); + return; } + + if (!Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) + { + MessageWriter retry = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.None, client.Id); + AmongUsClient.Instance.FinishRpcImmediately(retry); + } + + if (client.Character != null && client.Character.Data != null && (client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length >= 17) + Disco.ChangeColor(client.Character); } catch { } }, 4.5f, "green bean kick late task", false); @@ -237,7 +236,7 @@ internal static class OnPlayerJoinedPatch { if (!BanManager.TempBanWhiteList.Contains(client.GetHashedPuid())) BanManager.TempBanWhiteList.Add(client.GetHashedPuid()); - AmongUsClient.Instance.KickPlayer(client.Id, true); + AmongUsClient.Instance.KickPlayer(client.Id, false); Logger.SendInGame(string.Format(GetString("Message.KickedByNoFriendCode"), client.PlayerName)); Logger.Info($"TempBanned a player {client.PlayerName} without a friend code", "Temp Ban"); } @@ -570,7 +569,7 @@ internal static class SetColorPatch { LateTask.New(() => { - if (__instance != null && !Main.PlayerColors.ContainsKey(__instance.PlayerId)) + if (Options.KickSlowJoiningPlayers.GetBool() && __instance != null && !Main.PlayerColors.ContainsKey(__instance.PlayerId)) { ClientData client = __instance.GetClient(); diff --git a/Resources/Lang/en_US.json b/Resources/Lang/en_US.json index 0e00d11cb..45d71f2fe 100644 --- a/Resources/Lang/en_US.json +++ b/Resources/Lang/en_US.json @@ -936,6 +936,7 @@ "LadderDeath": "Fall From Ladders", "LadderDeathChance": "Fall To Death Chance", "GuesserDoesntDieOnMisguess": "Guesser Doesn't Die on Misguess", + "YouCanGuess": "<#00ffff>You can guess!\\n<#00a5ff>Use the <#ffffff>/bt [player id] [role] command to guess someone's role.\\nYou can see everyone's IDs next to their name, or type <#ffffff>/id.", "DisableTasks": "Disable Tasks", "DisableSwipeCardTask": "Disable Swipe Card Task", "DisableSubmitScanTask": "Disable Submit Scan Task", @@ -1177,6 +1178,7 @@ "AirshipAdditionalSpawn": "Additional Spawn Locations (Airship)", "CommsCamouflage": "Camouflage during Comms Sabotage", "CommsCamouflageDisableOnFungle": "Disable on Fungle", + "CommsCamouflageDisableOnMira": "Disable on MIRA HQ", "CommsCamouflageLimit": "Limit", "CommsCamouflageLimitSetChance": "Set Chance of Camouflaging (each time Comms are sabotaged)", "CommsCamouflageLimitChance": "Chance of Camouflaging", @@ -2938,6 +2940,7 @@ "JudgeCanTrialInfected": "Can trial Infected", "JudgeCanTrialContagious": "Can trial Contagious", "MessageRpcSizeLimit": "Message RPC Size Limit (Higher limit = increased risk of disconnects but fewer message splits - if you never disconnect randomly, up to 15000 can be safe)", + "KickSlowJoiningPlayers": "Kick Players Who Join Slowly To Prevent Disconnects (if they take more than 5 seconds to send their own data, they will be kicked automatically)", "AllCrewRolesHaveVanillaColor": "All <#8cffff>Crewmate roles have <#8cffff>the vanilla Crewmate color <#ffff00>(REQUIRES RESTART)", "StoreCompletedAchievementsOnEHRDatabase": "Store completed achievements on the EHR database (to maintain progress in case of a reinstall or an update)", "PostLobbyCodeToEHRDiscordServer": "Post the lobby code to the EHR Website", @@ -3590,7 +3593,7 @@ "Hypnotist.DoReportAfterHypnosisEnds": "Process reports that were blocked during hypnosis after the hypnosis expires", "Bane": "Bane", "BaneInfo": "Erase your killer's role", - "BaneInfoLong": "(Crewmates):\\nAs the Bane, if an impostor kills you, they will lose their abilities and become a regular impostor or shapeshifter.", + "BaneInfoLong": "(Crewmates):\\nAs the Bane, the player who kills you will lose all of their abilities and become a basic role.\\n\\nThat is:\\n- Regular impostor for impostors\\n- Regular crewmate for crewmates\\n- Coven Member for coven members\\n- Amnesiac for neutrals", "Transmitter": "Transmitter", "TransmitterInfo": "TP to nearest player by doing a task", "TransmitterInfoLong": "(Crewmates):\\nAs the Transmitter, when you complete a task, you teleport to the nearest player.", @@ -4065,7 +4068,7 @@ "Echo.DisableVentingWhileControlling": "Disable Venting While Controlling", "NiceHacker": "Hacker", "NiceHackerInfo": "Watch Admin Table from anywhere", - "NiceHackerInfoLong": "(Crewmates):\\nThis role is different for modded clients and vanilla clients.\\n\\nAs a modded client, whenever you open the map (and have ability uses left), the admin map will show up. You may be able to move while having the map open depending on settings.\\n\\nAs a vanilla client, you may enter a vent/pet to get a notification saying how many people are in different rooms on the ship (essentially admin map).", + "NiceHackerInfoLong": "(Crewmates):\\nThis role allows you to see the admin map from anywhere, but is different for modded clients and vanilla clients.\\n\\nAs a modded client, whenever you open the map (and have ability uses left), the admin map will show up. You may be able to move while having the map open depending on settings.\\n\\nAs a vanilla client, you may enter a vent/pet to get a notification saying how many people are in different rooms on the ship (essentially admin map).", "NiceHackerModdedClientAbilityUseSecondsMultiplier": "Modded clients, for 1 ability use, can view Admin map for", "NiceHackerModdedClientCanMoveWhileViewingMap": "Modded clients can move while looking at Admin map", "NiceHackerVanillaClientSeesInfoFor": "Vanilla clients see admin info notify for",