From 2e66fb54f9e7606ae267518b343a6cec3211a0af Mon Sep 17 00:00:00 2001 From: Ape <155788660+ApeMV@users.noreply.github.com> Date: Tue, 18 Aug 2026 21:00:42 +0200 Subject: [PATCH] v2.0.0 --- AmongUsRevamped.cs | 6 +- AmongUsRevamped.csproj | 2 +- Modules/BanManager.cs | 10 +- Modules/CommandHelper.cs | 4 +- Modules/CustomOptionsHolder.cs | 60 +++++++--- Modules/EACR.cs | 2 +- Modules/FixedUpdate.cs | 59 +++------ Modules/Rehost.cs | 6 + Modules/Utils.cs | 86 ++++++++++++- Patches/ChatPatches.cs | 199 ++++++++++++++++++++++++++++--- Patches/EndGamePatches.cs | 15 +-- Patches/IntroPatch.cs | 49 +++++++- Patches/JoinAndLeavePatches.cs | 42 ++++++- Patches/MeetingPatches.cs | 4 +- Patches/NumberOptionsPatch.cs | 5 + Patches/OnGameStartedPatch.cs | 113 ++++++++++++++++++ Patches/PlayerControlPatches.cs | 45 ++++--- README.md | 18 +-- Resources/Language/English.json | 16 ++- Resources/Language/Japanese.json | 12 +- Resources/Language/Russian.json | 12 +- 21 files changed, 617 insertions(+), 148 deletions(-) diff --git a/AmongUsRevamped.cs b/AmongUsRevamped.cs index 42f0c04..441969f 100644 --- a/AmongUsRevamped.cs +++ b/AmongUsRevamped.cs @@ -38,13 +38,13 @@ public partial class Main : BasePlugin public static ConfigEntry DisableCommandHelper { get; private set; } public static ConfigEntry DisableCompatibilityWarning { get; private set; } - public static NormalGameOptionsV10 NormalOptions => GameOptionsManager.Instance != null ? GameOptionsManager.Instance.currentNormalGameOptions : null; - public static HideNSeekGameOptionsV10 HideNSeekOptions => GameOptionsManager.Instance != null ? GameOptionsManager.Instance.currentHideNSeekGameOptions : null; + public static NormalGameOptionsV11 NormalOptions => GameOptionsManager.Instance != null ? GameOptionsManager.Instance.currentNormalGameOptions : null; + public static HideNSeekGameOptionsV11 HideNSeekOptions => GameOptionsManager.Instance != null ? GameOptionsManager.Instance.currentHideNSeekGameOptions : null; public static MapNames CurrentMap => (MapNames)NormalOptions.MapId; public static bool HasArgumentException; public static string CredentialsText; - public const string ModVersion = "v1.9.5"; + public const string ModVersion = "v2.0.0"; public static float GameTimer; public static float AfkTimer; diff --git a/AmongUsRevamped.csproj b/AmongUsRevamped.csproj index a1b1ee3..8a534f8 100644 --- a/AmongUsRevamped.csproj +++ b/AmongUsRevamped.csproj @@ -18,7 +18,7 @@ - + diff --git a/Modules/BanManager.cs b/Modules/BanManager.cs index dcb262c..a237028 100644 --- a/Modules/BanManager.cs +++ b/Modules/BanManager.cs @@ -29,7 +29,6 @@ public static class BanManager private static string TemplatePath = $"{DataPath}/AUR-DATA/Templates.txt"; private static string StartWordsPath = $"{DataPath}/AUR-DATA/Startwords.txt"; public static Dictionary Templates = new(); - public static List TempBanWhiteList = []; public static void Init() { try @@ -81,7 +80,7 @@ public static class BanManager public static void AddBanPlayer(ClientData player) { if (!AmongUsClient.Instance.AmHost || player == null) return; - if (!CheckBanList(player?.FriendCode, player?.GetHashedPuid()) && !TempBanWhiteList.Contains(player?.GetHashedPuid())) + if (!CheckBanList(player?.FriendCode, player?.GetHashedPuid())) { if (player?.GetHashedPuid() != "" && player?.GetHashedPuid() != null && player?.GetHashedPuid() != "e3b0cb855") { @@ -106,13 +105,6 @@ public static class BanManager Logger.Info($"{player.PlayerName} was in the BanList and has been banned", "BanListBan"); return; } - if (TempBanWhiteList.Contains(player?.GetHashedPuid())) - { - AmongUsClient.Instance.KickPlayer(player.Id, true); - //This should not happen - Logger.Info($"{player.PlayerName} was in the Temporary BanList", "TempBan"); - return; - } } public static bool CheckBanList(string code, string hashedpuid = "") { diff --git a/Modules/CommandHelper.cs b/Modules/CommandHelper.cs index b45924d..fc32bd2 100644 --- a/Modules/CommandHelper.cs +++ b/Modules/CommandHelper.cs @@ -26,6 +26,7 @@ internal static class CommandHelper new Cmd { Name = new[] { "/sns" }, Prompt = "/sns", Description = Translator.Get("snsDesc"), HasVariable = false }, new Cmd { Name = new[] { "/shiftandseek" }, Prompt = "/shiftandseek", Description = Translator.Get("snsDesc"), HasVariable = false }, + new Cmd { Name = new[] { "/sns2" }, Prompt = "/sns2", Description = Translator.Get("snsTwoDesc"), HasVariable = false }, new Cmd { Name = new[] { "/0kc" }, Prompt = "/0kc", Description = Translator.Get("0kcDesc"), HasVariable = false }, new Cmd { Name = new[] { "/0killcooldown" }, Prompt = "/0killcooldown", Description = Translator.Get("0kcDesc"), HasVariable = false }, @@ -82,7 +83,8 @@ internal static class CommandHelper new Cmd { Name = new[] { "/clearseeker" }, Prompt = "/clearseeker", Description = Translator.Get("clearSeekerDesc"), HasVariable = false }, new Cmd { Name = new[] { "/mapvote" }, Prompt = "/mapvote", Description = Translator.Get("mapVoteDesc"), HasVariable = false }, - new Cmd { Name = new[] { "/allmapvote" }, Prompt = "/allmapvote", Description = Translator.Get("allMapVoteDesc"), HasVariable = false } + new Cmd { Name = new[] { "/allmapvote" }, Prompt = "/allmapvote", Description = Translator.Get("allMapVoteDesc"), HasVariable = false }, + new Cmd { Name = new[] { "/gmvote" }, Prompt = "/gmvote", Description = Translator.Get("modeVoteDesc"), HasVariable = false } }; [HarmonyPostfix] diff --git a/Modules/CustomOptionsHolder.cs b/Modules/CustomOptionsHolder.cs index a4cb1c1..27fffd2 100644 --- a/Modules/CustomOptionsHolder.cs +++ b/Modules/CustomOptionsHolder.cs @@ -104,6 +104,8 @@ namespace AmongUsRevamped public static OptionItem TimeBeforeMapPoll; public static OptionItem AutomaticAllMapPoll; public static OptionItem TimeBeforeAllMapPoll; + public static OptionItem AutomaticModePoll; + public static OptionItem TimeBeforeModePoll; public static OptionItem TabGroupAutostart; public static OptionItem AutoStartTimer; @@ -121,6 +123,7 @@ namespace AmongUsRevamped public static OptionItem TabGroupMisc; public static OptionItem MapVoteDuration; + public static OptionItem ModeVoteDuration; public static OptionItem StartCountdown; public static OptionItem ColorCommandLevel; public static OptionItem AllowFortegreen; @@ -149,8 +152,9 @@ namespace AmongUsRevamped public static OptionItem DisableCloseDoor; public static OptionItem TabGroupGameplayGeneral; + public static OptionItem ShieldFirstDeath; + public static OptionItem ShieldFirstDeathDuration; public static OptionItem DisableAnnoyingMeetingCalls; - public static OptionItem ChangeDecontaminationTime; public static OptionItem DecontaminationTimeOnMiraHQ; public static OptionItem DecontaminationDoorOpenTimeOnMiraHQ; @@ -294,6 +298,8 @@ namespace AmongUsRevamped public static OptionItem TabGroupSNS; public static OptionItem SNSChatInGame; public static OptionItem SNSChatInGameExtend; + public static OptionItem SNSChatInGameExtend2; + public static OptionItem SNSChatInGameFast; public static OptionItem CrewAutoWinsGameAfter; public static OptionItem MisfiresToSuicide; public static OptionItem SNSDisableSabotage; @@ -313,6 +319,7 @@ namespace AmongUsRevamped public static OptionItem TabGroupPNS; public static OptionItem PNSChatInGame; public static OptionItem PNSChatInGameExtend; + public static OptionItem PNSChatInGameFast; public static OptionItem PNSCrewAutoWinsGameAfter; public static OptionItem BadMoveTimeToSuicide; public static OptionItem PNSDisableSabotage; @@ -410,6 +417,10 @@ namespace AmongUsRevamped TimeBeforeAllMapPoll = IntegerOptionItem.Create(60157, Translator.Get("timeBeforeMapPoll"), new(10, 300, 5), 10, TabGroup.SystemSettings, false) .SetValueFormat(OptionFormat.Seconds) .SetParent(AutomaticAllMapPoll); + AutomaticModePoll = BooleanOptionItem.Create(60158, Translator.Get("automaticModePoll"), false, TabGroup.SystemSettings, false); + TimeBeforeModePoll = IntegerOptionItem.Create(60159, Translator.Get("timeBeforeMapPoll"), new(10, 300, 5), 20, TabGroup.SystemSettings, false) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(AutomaticModePoll); TabGroupAutostart = TextOptionItem.Create(64425, Translator.Get("tabGroupAutostart"), TabGroup.SystemSettings) .SetColor(Color.yellow); @@ -438,7 +449,10 @@ namespace AmongUsRevamped MapVoteDuration = IntegerOptionItem.Create(60379, Translator.Get("mapVoteDuration"), new(5, 60, 5), 20, TabGroup.SystemSettings, false) .SetValueFormat(OptionFormat.Seconds) .SetColor(Color.red); - StartCountdown = IntegerOptionItem.Create(60380, Translator.Get("startCountdown"), new(1, 5, 1), 5, TabGroup.SystemSettings, false) + ModeVoteDuration = IntegerOptionItem.Create(60380, Translator.Get("modeVoteDuration"), new(5, 60, 5), 20, TabGroup.SystemSettings, false) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(Color.red); + StartCountdown = IntegerOptionItem.Create(60381, Translator.Get("startCountdown"), new(1, 5, 1), 5, TabGroup.SystemSettings, false) .SetValueFormat(OptionFormat.Seconds) .SetColor(Color.red); AllowFortegreen = BooleanOptionItem.Create(60382, Translator.Get("allowFortegreen"), false, TabGroup.SystemSettings, false) @@ -456,36 +470,40 @@ namespace AmongUsRevamped TabGroupSNS = TextOptionItem.Create(70050, Translator.Get("tabGroupSNS"), TabGroup.GamemodeSettings) .SetColor(Color.green); SNSChatInGame = BooleanOptionItem.Create(70051, Translator.Get("snsChatInGame"), false, TabGroup.GamemodeSettings, false); - SNSChatInGameExtend = BooleanOptionItem.Create(70052, Translator.Get("snsChatInGameExtend"), false, TabGroup.GamemodeSettings, false) + SNSChatInGameExtend = BooleanOptionItem.Create(70052, Translator.Get("snsChatInGameExtend"), true, TabGroup.GamemodeSettings, false) .SetParent(SNSChatInGame); - MisfiresToSuicide = IntegerOptionItem.Create(70053, Translator.Get("misfiresToSuicide"), new(1, 10, 1), 1, TabGroup.GamemodeSettings, false); - CrewAutoWinsGameAfter = IntegerOptionItem.Create(70054, Translator.Get("crewAutoWinsGameAfter"), new(60, 600, 10), 300, TabGroup.GamemodeSettings, false) + SNSChatInGameExtend2 = BooleanOptionItem.Create(70053, Translator.Get("snsChatInGameExtendTwo"), true, TabGroup.GamemodeSettings, false) + .SetParent(SNSChatInGame); + SNSChatInGameFast = BooleanOptionItem.Create(70054, Translator.Get("snsChatInGameFast"), false, TabGroup.GamemodeSettings, false) + .SetParent(SNSChatInGame); + MisfiresToSuicide = IntegerOptionItem.Create(70055, Translator.Get("misfiresToSuicide"), new(1, 10, 1), 1, TabGroup.GamemodeSettings, false); + CrewAutoWinsGameAfter = IntegerOptionItem.Create(70056, Translator.Get("crewAutoWinsGameAfter"), new(60, 600, 10), 300, TabGroup.GamemodeSettings, false) .SetValueFormat(OptionFormat.Seconds); - SNSDisableSabotage = BooleanOptionItem.Create(70055, Translator.Get("snsDisableSabotage"), true, TabGroup.GamemodeSettings, false) + SNSDisableSabotage = BooleanOptionItem.Create(70057, Translator.Get("snsDisableSabotage"), true, TabGroup.GamemodeSettings, false) .SetColor(Color.red); - SNSDisableReactor = BooleanOptionItem.Create(70056, Translator.Get("snsDisableReactor"), true, TabGroup.GamemodeSettings, false) + SNSDisableReactor = BooleanOptionItem.Create(70058, Translator.Get("snsDisableReactor"), true, TabGroup.GamemodeSettings, false) .SetParent(SNSDisableSabotage) .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - SNSDisableOxygen = BooleanOptionItem.Create(70057, Translator.Get("snsDisableOxygen"), true, TabGroup.GamemodeSettings, false) + SNSDisableOxygen = BooleanOptionItem.Create(70059, Translator.Get("snsDisableOxygen"), true, TabGroup.GamemodeSettings, false) .SetParent(SNSDisableSabotage) .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - SNSDisableLights = BooleanOptionItem.Create(70058, Translator.Get("snsDisableLights"), true, TabGroup.GamemodeSettings, false) + SNSDisableLights = BooleanOptionItem.Create(70060, Translator.Get("snsDisableLights"), true, TabGroup.GamemodeSettings, false) .SetParent(SNSDisableSabotage) .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - SNSDisableComms = BooleanOptionItem.Create(70059, Translator.Get("snsDisableComms"), true, TabGroup.GamemodeSettings, false) + SNSDisableComms = BooleanOptionItem.Create(70061, Translator.Get("snsDisableComms"), true, TabGroup.GamemodeSettings, false) .SetParent(SNSDisableSabotage) .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - SNSDisableHeli = BooleanOptionItem.Create(70060, Translator.Get("snsDisableHeli"), true, TabGroup.GamemodeSettings, false) + SNSDisableHeli = BooleanOptionItem.Create(70062, Translator.Get("snsDisableHeli"), true, TabGroup.GamemodeSettings, false) .SetParent(SNSDisableSabotage) .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - SNSDisableMushroomMixup = BooleanOptionItem.Create(70061, Translator.Get("snsDisableMushroomMixup"), true, TabGroup.GamemodeSettings, false) + SNSDisableMushroomMixup = BooleanOptionItem.Create(70063, Translator.Get("snsDisableMushroomMixup"), true, TabGroup.GamemodeSettings, false) .SetParent(SNSDisableSabotage) .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - SNSDisableCloseDoor = BooleanOptionItem.Create(70062, Translator.Get("snsDisableCloseDoor"), true, TabGroup.GamemodeSettings, false) + SNSDisableCloseDoor = BooleanOptionItem.Create(70064, Translator.Get("snsDisableCloseDoor"), true, TabGroup.GamemodeSettings, false) .SetColor(Color.red); - SNSImpostorsCanVent = BooleanOptionItem.Create(70063, Translator.Get("snsImpostorCanVent"), true, TabGroup.GamemodeSettings, false); + SNSImpostorsCanVent = BooleanOptionItem.Create(70065, Translator.Get("snsImpostorCanVent"), true, TabGroup.GamemodeSettings, false); - TabGroupSpeedrun = TextOptionItem.Create(70075, Translator.Get("tabGroupSpeedrun"), TabGroup.GamemodeSettings) + TabGroupSpeedrun = TextOptionItem.Create(70076, Translator.Get("tabGroupSpeedrun"), TabGroup.GamemodeSettings) .SetColor(Color.green); GameAutoEndsAfter = IntegerOptionItem.Create(70077, Translator.Get("gameAutoEndsAfter"), new(0, 600, 10), 300, TabGroup.GamemodeSettings, false) .SetValueFormat(OptionFormat.Seconds); @@ -493,8 +511,10 @@ namespace AmongUsRevamped TabGroupPNS = TextOptionItem.Create(70100, Translator.Get("tabGroupPNS"), TabGroup.GamemodeSettings) .SetColor(Color.green); - PNSChatInGame = BooleanOptionItem.Create(70102, Translator.Get("snsChatInGame"), false, TabGroup.GamemodeSettings, false); - PNSChatInGameExtend = BooleanOptionItem.Create(70103, Translator.Get("snsChatInGameExtend"), false, TabGroup.GamemodeSettings, false) + PNSChatInGame = BooleanOptionItem.Create(70101, Translator.Get("snsChatInGame"), false, TabGroup.GamemodeSettings, false); + PNSChatInGameExtend = BooleanOptionItem.Create(70102, Translator.Get("snsChatInGameExtend"), false, TabGroup.GamemodeSettings, false) + .SetParent(PNSChatInGame); + PNSChatInGameFast = BooleanOptionItem.Create(70103, Translator.Get("snsChatInGameFast"), false, TabGroup.GamemodeSettings, false) .SetParent(PNSChatInGame); BadMoveTimeToSuicide = IntegerOptionItem.Create(70104, Translator.Get("badMoveTimeToSuicide"), new(1, 30, 1), 3, TabGroup.GamemodeSettings, false) .SetValueFormat(OptionFormat.Seconds); @@ -554,7 +574,11 @@ namespace AmongUsRevamped TabGroupGameplayGeneral = TextOptionItem.Create(60564, Translator.Get("tabGroupGameplayGeneral"), TabGroup.ModSettings) .SetColor(Color.blue); - DisableAnnoyingMeetingCalls = BooleanOptionItem.Create(60565, Translator.Get("disableAnnoyingMeetingCalls"), false, TabGroup.ModSettings, false); + ShieldFirstDeath = BooleanOptionItem.Create(60565, Translator.Get("shieldFirstDeath"), false, TabGroup.ModSettings, false); + ShieldFirstDeathDuration = IntegerOptionItem.Create(60566, Translator.Get("shieldFirstDeathDuration"), new(10, 120, 5), 30, TabGroup.ModSettings, false) + .SetParent(ShieldFirstDeath) + .SetValueFormat(OptionFormat.Seconds); + DisableAnnoyingMeetingCalls = BooleanOptionItem.Create(60567, Translator.Get("disableAnnoyingMeetingCalls"), false, TabGroup.ModSettings, false); ChangeDecontaminationTime = BooleanOptionItem.Create(60550, Translator.Get("changeDecontaminationTime"), false, TabGroup.ModSettings, false); DecontaminationTimeOnMiraHQ = FloatOptionItem.Create(60551, Translator.Get("decontaminationTimeOnMiraHQ"), new(0.5f, 10f, 0.25f), 3f, TabGroup.ModSettings, false) diff --git a/Modules/EACR.cs b/Modules/EACR.cs index 6bcebfd..de89dd8 100644 --- a/Modules/EACR.cs +++ b/Modules/EACR.cs @@ -184,7 +184,7 @@ internal class EACR goto YesCheat; } - if (Utils.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating) + if (Utils.IsMeeting && MeetingHud.Instance.state != MeetingHud.MeetingStates.Animating) { Logger.SendInGame($"{player.Data.PlayerName} might have called an invalid sabotage (cheating)"); Logger.Info($" {player.Data.PlayerName} might have called an invalid sabotage (cheating)", "EACR"); diff --git a/Modules/FixedUpdate.cs b/Modules/FixedUpdate.cs index 5e03674..f538fe7 100644 --- a/Modules/FixedUpdate.cs +++ b/Modules/FixedUpdate.cs @@ -119,54 +119,17 @@ public static class FixedUpdate } } - // We check if any SnS Shapshifter is currently nearby their correct target. - // If so, the global Kill Cooldown is set to 1 and all killing actions may proceed. - // If not, the global Kill Cooldown is set to 0. - // While this value is 0, the game stops the kill cooldown from decreasing. - // If the Kill Cooldown value is 0 by default (which it will be at the start of a match), Among Us does not allow kills at all. - // This means most of the time, we can successfully prevent invalid kills. - // The only problem is that if one Shapeshifter is nearby their correct target, while another is not, the second Shapeshifter can kill too. if (Options.Gamemode.GetValue() == 1 && !Utils.isHideNSeek && !Utils.IsLobby) { - bool shapeshifterNearTarget = false; - foreach (var p in PlayerControl.AllPlayerControls) { - if (p.Data == null || p.Data.IsDead || p.shapeshiftTargetPlayerId == byte.MaxValue) continue; + if (p.Data == null || p.Data.IsDead || p.Data.RoleType == RoleTypes.Shapeshifter) continue; - PlayerControl shapeshiftTarget = null; - - foreach (var t in PlayerControl.AllPlayerControls) + if (p.protectedByGuardianId == -1) { - if (t.Data == null || t.Data.IsDead) continue; - - if (t.Data.PlayerId == p.shapeshiftTargetPlayerId) - { - shapeshiftTarget = t; - break; - } - } - - if (shapeshiftTarget == null) continue; - - float distance = Vector2.Distance(p.GetTruePosition(), shapeshiftTarget.GetTruePosition()); - - if (distance <= 3f) - { - shapeshifterNearTarget = true; - break; + p.RpcProtectPlayer(p, p.cosmetics.ColorId); } } - - - float newCooldown = shapeshifterNearTarget ? 1f : 0f; - - if (Main.NormalOptions.KillCooldown != newCooldown) - { - Main.NormalOptions.KillCooldown = newCooldown; - OptionManager.SyncGameOptions(); - } - } } @@ -246,7 +209,13 @@ class FixedUpdateInGamePatch case 1: // SnS if (Utils.InGame) return; - if (Main.NormalOptions.KillCooldown != 1f) Main.NormalOptions.KillCooldown = 1f; + if (Main.NormalOptions.KillCooldown != 2.5f) + { + Main.NormalOptions.KillCooldown = 2.5f; + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 3, 100); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Phantom, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Viper, 0, 0); + } break; case 2: // Speedrun @@ -254,7 +223,13 @@ class FixedUpdateInGamePatch case 3: // PnS if (Utils.InGame) return; - if (Main.NormalOptions.KillCooldown != 1f) Main.NormalOptions.KillCooldown = 1f; + if (Main.NormalOptions.KillCooldown != 1f) + { + Main.NormalOptions.KillCooldown = 1f; + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Phantom, 3, 100); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Viper, 0, 0); + } break; } diff --git a/Modules/Rehost.cs b/Modules/Rehost.cs index 4a2337c..cd16b7e 100644 --- a/Modules/Rehost.cs +++ b/Modules/Rehost.cs @@ -13,6 +13,12 @@ namespace AmongUsRevamped { Logger.Info($" Disconnected due to {reason}/{stringReason}, ping:{__instance.Ping})", "DisconnectManager"); + if (!EOSManager.Instance.loginFlowFinished || EOSManager.Instance.authExpiredCallbackTriggered) + { + Logger.Info("Attempted to log in using EOS", "DisconnectManager"); + EOSManager.Instance.AdultPermissionsFlow(); + } + if (!Options.Rehost.GetBool() || reason == DisconnectReasons.NewConnection || reason == DisconnectReasons.ConnectionLimit || reason == DisconnectReasons.ExitGame) return; _ = new LateTask(() => diff --git a/Modules/Utils.cs b/Modules/Utils.cs index ea0ed64..7523fc6 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -42,6 +42,7 @@ public static class Utils public static bool HandlingGameEnd; public static byte CustomGameOverReason; public static bool CanCallMeetings; + public static bool HasMurdered; public static string ColorString(Color32 color, string str) => $"<#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}>{str}"; public static string ColorToHex(Color32 color) => $"#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}"; @@ -206,9 +207,11 @@ public static class Utils Main.GameTimer = 0f; Main.AfkTimer = 0f; FixedUpdate.Position.Clear(); + NormalGameEndChecker.canUpdateWinnerText = true; MurderPlayerPatch.misfireCount.Clear(); MurderPlayerPatch.killCount.Clear(); + HasMurdered = false; LateTask.Tasks.RemoveAll(x => x.name != "RehostManager"); NormalGameEndChecker.ImpCheckComplete = false; @@ -240,6 +243,13 @@ public static class Utils dleksVotes = 0; HasVoted.Clear(); MapVoteActive = false; + + standardVotes = 0; + noKillCooldownVotes = 0; + speedrunVotes = 0; + shiftAndSeekVotes = 0; + poofAndSeekVotes = 0; + ModeVoteActive = false; } public static PlayerControl[] AllAlivePlayerControls @@ -598,7 +608,7 @@ public static class Utils public static readonly List HasVoted = []; public static void MapVote(bool dleks, bool moderator) { - if (MapVoteActive) return; + if (MapVoteActive || ModeVoteActive) return; DleksEnabled = dleks; MapVoteActive = true; @@ -676,4 +686,78 @@ public static class Utils MapVoteActive = false; }, Options.MapVoteDuration.GetInt(), "MapPoll"); } + + public static int standardVotes; + public static int noKillCooldownVotes; + public static int speedrunVotes; + public static int shiftAndSeekVotes; + public static int poofAndSeekVotes; + public static bool ModeVoteActive; + public static readonly List HasModeVoted = []; + public static void ModeVote(bool moderator) + { + if (ModeVoteActive || MapVoteActive) return; + + ModeVoteActive = true; + + if (moderator) Utils.ModeratorChatCommand("A Gamemode vote is active!\n\nA: None\nB: No Kill Cooldown\nC: Speedrun\nD: SnS\nE: PnS\n\nType /vote 'letter' to vote.", "", false); + else Utils.ChatCommand(DestroyableSingleton.Instance.Chat, "A Gamemode vote is active!\n\nA: None\nB: No Kill Cooldown\nC: Speedrun\nD: SnS\nE: PnS\n\nType /vote 'letter' to vote.", "", false); + + new LateTask(() => + { + int highest = standardVotes; + string winner = "None"; + byte modeId = 0; + bool tie = false; + + void Check(int votes, string name, byte id) + { + if (votes > highest) + { + highest = votes; + winner = name; + modeId = id; + tie = false; + } + else if (votes == highest) + { + tie = true; + } + } + + Check(noKillCooldownVotes, "0 Kill Cooldown", 0); + Check(speedrunVotes, "Speedrun", 2); + Check(shiftAndSeekVotes, "Shift and Seek", 1); + Check(poofAndSeekVotes, "Poof and Seek", 3); + + string voteResults = $"Standard: {standardVotes}\n" + $"No Kill Cooldown: {noKillCooldownVotes}\n" + $"Speedrun: {speedrunVotes}\n" + $"Shift and Seek: {shiftAndSeekVotes}\n" + $"Poof and Seek: {poofAndSeekVotes}"; + if (highest == 0) + { + ModeratorChatCommand("We have a winner! Just kidding. No one voted.", "", false); + } + else if (tie) + { + ModeratorChatCommand($"Gamemode vote tied. No new mode selected.\n\n{voteResults}", "", false); + } + else + { + ModeratorChatCommand(SendChatPatch.ConvertNum($"Winner: {winner} ({highest})\n\n{voteResults}"), "", false); + Options.Gamemode.SetValue(modeId); + + if (winner == "0 Kill Cooldown") + { + Main.NormalOptions.KillCooldown = 0.01f; + } + + } + standardVotes = 0; + noKillCooldownVotes = 0; + speedrunVotes = 0; + shiftAndSeekVotes = 0; + poofAndSeekVotes = 0; + HasVoted.Clear(); + + ModeVoteActive = false; + }, Options.ModeVoteDuration.GetInt(), "ModePoll"); + } } \ No newline at end of file diff --git a/Patches/ChatPatches.cs b/Patches/ChatPatches.cs index 47997d4..3567c78 100644 --- a/Patches/ChatPatches.cs +++ b/Patches/ChatPatches.cs @@ -420,12 +420,92 @@ internal static class SendChatPatch } } - if (text == "/vote a" && Utils.MapVoteActive && !Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) {Utils.skeldVotes++; Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId);} - if (text == "/vote b" && Utils.MapVoteActive && !Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) {Utils.miraVotes++; Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId);} - if (text == "/vote c" && Utils.MapVoteActive && !Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) {Utils.polusVotes++; Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId);} - if (text == "/vote d" && Utils.MapVoteActive && !Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) {Utils.airshipVotes++; Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId);} - if (text == "/vote e" && Utils.MapVoteActive && !Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) {Utils.fungleVotes++; Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId);} - if (text == "/vote f" && Utils.MapVoteActive && !Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId) && Utils.DleksEnabled) {Utils.dleksVotes++; Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId);} + if (text == "/gmvote") + { + if (Utils.IsLobby) + { + Utils.ModeVote(false); + + __instance.freeChatField.textArea.Clear(); + __instance.freeChatField.textArea.SetText(string.Empty); + return false; + } + } + + if (text == "/vote a") + { + if (Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) return true; + if (Utils.MapVoteActive && !Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) + { + Utils.skeldVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.standardVotes++; + } + Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId); + } + if (text == "/vote b") + { + if (Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) return true; + if (Utils.MapVoteActive) + { + Utils.miraVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.noKillCooldownVotes++; + } + Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId); + } + if (text == "/vote c") + { + if (Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) return true; + if (Utils.MapVoteActive) + { + Utils.polusVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.speedrunVotes++; + } + Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId); + } + if (text == "/vote d") + { + if (Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) return true; + if (Utils.MapVoteActive) + { + Utils.airshipVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.shiftAndSeekVotes++; + } + Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId); + } + if (text == "/vote e") + { + if (Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) return true; + if (Utils.MapVoteActive) + { + Utils.fungleVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.poofAndSeekVotes++; + } + Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId); + } + if (text == "/vote f") + { + if (Utils.HasVoted.Contains(PlayerControl.LocalPlayer.Data.PlayerId)) return true; + if (Utils.MapVoteActive && Utils.DleksEnabled) + { + Utils.dleksVotes++; + } + Utils.HasVoted.Add(PlayerControl.LocalPlayer.Data.PlayerId); + } if (text == "/l" || text == "/lastgame") { @@ -451,7 +531,12 @@ internal static class SendChatPatch if (text == "/sns" || text == "/shiftandseek") { - Utils.ChatCommand(__instance, "Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by doing tasks or surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing everyone\n{Options.MisfiresToSuicide.GetInt()} wrong kill(s) = suicide", true); + Utils.ChatCommand(__instance, "Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by tasks/surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing\nWrong kills are blocked. Kill animations are off", true); + return false; + } + if (text == "/sns2" || text == "/shiftandseek2") + { + Utils.ChatCommand(__instance, "Correctly killing a target will directly turn them into a ghost and leave no dead body", "", false); return false; } @@ -479,7 +564,7 @@ internal static class SendChatPatch break; case 1: - Utils.ChatCommand(__instance, "Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by doing tasks or surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing everyone\n{Options.MisfiresToSuicide.GetInt()} wrong kill(s) = suicide", true); + Utils.ChatCommand(__instance, "Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by tasks/surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing\nWrong kills are blocked. Kill animations are off", true); break; case 2: @@ -771,13 +856,90 @@ public static class RPCHandlerPatch Utils.MapVote(true, true); } } + + if (text == "/gmvote") + { + if (Utils.IsLobby) + { + if (Utils.CheckAccessLevel(__instance.Data.FriendCode) < Options.SlashMapAndSeekerCmd.GetValue()) return; + Utils.ModeVote(true); + } + } - if (text == "/vote a" && Utils.MapVoteActive && !Utils.HasVoted.Contains(__instance.Data.PlayerId)) {Utils.skeldVotes++; Utils.HasVoted.Add(__instance.Data.PlayerId);} - if (text == "/vote b" && Utils.MapVoteActive && !Utils.HasVoted.Contains(__instance.Data.PlayerId)) {Utils.miraVotes++; Utils.HasVoted.Add(__instance.Data.PlayerId);} - if (text == "/vote c" && Utils.MapVoteActive && !Utils.HasVoted.Contains(__instance.Data.PlayerId)) {Utils.polusVotes++; Utils.HasVoted.Add(__instance.Data.PlayerId);} - if (text == "/vote d" && Utils.MapVoteActive && !Utils.HasVoted.Contains(__instance.Data.PlayerId)) {Utils.airshipVotes++; Utils.HasVoted.Add(__instance.Data.PlayerId);} - if (text == "/vote e" && Utils.MapVoteActive && !Utils.HasVoted.Contains(__instance.Data.PlayerId)) {Utils.fungleVotes++; Utils.HasVoted.Add(__instance.Data.PlayerId);} - if (text == "/vote f" && Utils.MapVoteActive && !Utils.HasVoted.Contains(__instance.Data.PlayerId) && Utils.DleksEnabled) {Utils.dleksVotes++; Utils.HasVoted.Add(__instance.Data.PlayerId);} + if (text == "/vote a") + { + if (Utils.HasVoted.Contains(__instance.Data.PlayerId)) return; + if (Utils.MapVoteActive) + { + Utils.skeldVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.standardVotes++; + } + Utils.HasVoted.Add(__instance.Data.PlayerId); + } + if (text == "/vote b") + { + if (Utils.HasVoted.Contains(__instance.Data.PlayerId)) return; + if (Utils.MapVoteActive) + { + Utils.miraVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.noKillCooldownVotes++; + } + Utils.HasVoted.Add(__instance.Data.PlayerId); + } + if (text == "/vote c") + { + if (Utils.HasVoted.Contains(__instance.Data.PlayerId)) return; + if (Utils.MapVoteActive) + { + Utils.polusVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.speedrunVotes++; + } + Utils.HasVoted.Add(__instance.Data.PlayerId); + } + if (text == "/vote d") + { + if (Utils.HasVoted.Contains(__instance.Data.PlayerId)) return; + if (Utils.MapVoteActive) + { + Utils.airshipVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.shiftAndSeekVotes++; + } + Utils.HasVoted.Add(__instance.Data.PlayerId); + } + if (text == "/vote e") + { + if (Utils.HasVoted.Contains(__instance.Data.PlayerId)) return; + if (Utils.MapVoteActive) + { + Utils.fungleVotes++; + } + if (Utils.ModeVoteActive) + { + Utils.poofAndSeekVotes++; + } + Utils.HasVoted.Add(__instance.Data.PlayerId); + } + if (text == "/vote f") + { + if (Utils.HasVoted.Contains(__instance.Data.PlayerId)) return; + if (Utils.MapVoteActive && Utils.DleksEnabled) + { + Utils.dleksVotes++; + } + Utils.HasVoted.Add(__instance.Data.PlayerId); + } if (text == "/l" || text == "/lastgame") { @@ -800,7 +962,12 @@ public static class RPCHandlerPatch if (text == "/sns" || text == "/shiftandseek") { if (Utils.CheckAccessLevel(__instance.Data.FriendCode) < Options.SlashRolesAndGamemodeCmd.GetValue()) return; - Utils.ModeratorChatCommand("Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by doing tasks or surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing everyone\n{Options.MisfiresToSuicide.GetInt()} wrong kill(s) = suicide", true); + Utils.ModeratorChatCommand("Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by tasks/surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing\nWrong kills are blocked. Kill animations are off", true); + } + if (text == "/sns2" || text == "/shiftandseek2") + { + if (Utils.CheckAccessLevel(__instance.Data.FriendCode) < Options.SlashRolesAndGamemodeCmd.GetValue()) return; + Utils.ModeratorChatCommand("Correctly killing a target will directly turn them into a ghost and leave no dead body", "", false); } if (text == "/sr" || text == "/speedrun") @@ -828,7 +995,7 @@ public static class RPCHandlerPatch break; case 1: - Utils.ModeratorChatCommand("Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by doing tasks or surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing everyone\n{Options.MisfiresToSuicide.GetInt()} wrong kill(s) = suicide", true); + Utils.ModeratorChatCommand("Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by tasks/surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing\nWrong kills are blocked. Kill animations are off", true); break; case 2: diff --git a/Patches/EndGamePatches.cs b/Patches/EndGamePatches.cs index 1d2ca9b..1940b55 100644 --- a/Patches/EndGamePatches.cs +++ b/Patches/EndGamePatches.cs @@ -27,10 +27,11 @@ class NormalGameEndChecker public static bool ImpCheckComplete; public static string LastWinReason = ""; public static List imps = new List(); + public static string firstDeath; + public static string firstDeathString => firstDeath != null ? $"\n\nFirst death: {firstDeath}" : ""; public static bool Prefix() { - if (Options.NoGameEnd.GetBool() || Options.Gamemode.GetValue() == 2 || Utils.HandlingGameEnd) return false; var allPlayers = PlayerControl.AllPlayerControls.ToArray(); @@ -55,14 +56,14 @@ class NormalGameEndChecker if (Winner == "SnSTimer") { - LastWinReason = $"Crewmates Win! (Survived {Options.CrewAutoWinsGameAfter.GetInt()}s)\n\n Impostors: " + impostorList; + LastWinReason = $"Crewmates Win! (Survived {Options.CrewAutoWinsGameAfter.GetInt()}s)\n\n Impostors: " + impostorList + firstDeathString; canUpdateWinnerText = false; return; } if (Winner == "PnSTimer") { - LastWinReason = $"Crewmates Win! (Survived {Options.CrewAutoWinsGameAfter.GetInt()}s)\n\n Impostors: " + impostorList; + LastWinReason = $"Crewmates Win! (Survived {Options.CrewAutoWinsGameAfter.GetInt()}s)\n\n Impostors: " + impostorList + firstDeathString; canUpdateWinnerText = false; return; } @@ -76,22 +77,22 @@ class NormalGameEndChecker if ((Options.Gamemode.GetValue() != 2 && Utils.AliveImpostors == 0) || Winner == "Crewmate") { - LastWinReason = "Crewmates Win!\n\nImpostors: " + impostorList; + LastWinReason = "Crewmates Win!\n\nImpostors: " + impostorList + firstDeathString; canUpdateWinnerText = false; } else if ((Options.Gamemode.GetValue() != 2 && Utils.AliveImpostors >= Utils.AliveCrewmates) || Winner == "Impostor") { - LastWinReason = "Impostors Win!\n\nImpostors: " + impostorList; + LastWinReason = "Impostors Win!\n\nImpostors: " + impostorList + firstDeathString; canUpdateWinnerText = false; } else if (GameData.Instance != null && GameData.Instance.TotalTasks > 0 && GameData.Instance.CompletedTasks >= GameData.Instance.TotalTasks || Winner == "CrewmateTasks") { - LastWinReason = $"Crewmates Win! ({Options.TaskPercentNeededToWin.GetInt()}% tasks completed)\n\nImpostors: " + impostorList; + LastWinReason = $"Crewmates Win! ({Options.TaskPercentNeededToWin.GetInt()}% tasks completed)\n\nImpostors: " + impostorList + firstDeathString; canUpdateWinnerText = false; } else if (Options.Gamemode.GetValue() < 1 || Winner == "ImpostorSabotage") { - LastWinReason = "Impostors Win! (Sabotage)\n\n Impostors: " + impostorList; + LastWinReason = "Impostors Win! (Sabotage)\n\n Impostors: " + impostorList + firstDeathString; } } } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index bc28352..8e329bb 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -19,6 +19,29 @@ internal static class CoShowIntroPatch foreach (var p in PlayerControl.AllPlayerControls) { + p.protectedByGuardianId = -1; + + if (NormalGameEndChecker.firstDeath != null && NormalGameEndChecker.firstDeath == p.Data.PlayerName && Options.ShieldFirstDeath.GetBool() && Options.Gamemode.GetValue() != 1) + { + if (Main.NormalOptions.roleOptions.TryGetRoleOptions(RoleTypes.GuardianAngel, out var GuardianAngelOptions)) + { + var oldGA = GuardianAngelOptions.ImpostorsCanSeeProtect; + var oldGAProt = GuardianAngelOptions.ProtectionDurationSeconds; + GuardianAngelOptions.ProtectionDurationSeconds = Options.ShieldFirstDeathDuration.GetFloat(); + GuardianAngelOptions.ImpostorsCanSeeProtect = true; + + _ = new LateTask(() => + { GuardianAngelOptions.ImpostorsCanSeeProtect = oldGA; + GuardianAngelOptions.ProtectionDurationSeconds = oldGAProt + 6; + OptionManager.SyncGameOptions(); + }, Options.ShieldFirstDeathDuration.GetFloat(), "RevertGAProtTime"); + } + + OptionManager.SyncGameOptions(); + p.RpcProtectPlayer(p, p.cosmetics.ColorId); + Logger.Info($" {p.Data.PlayerName} has been granted a shield for {Options.ShieldFirstDeathDuration.GetInt()}s", "RoleInfo"); + } + p.cosmetics.nameText.text = p.Data.PlayerName; MurderPlayerPatch.killCount[p.PlayerId] = 0; @@ -35,12 +58,28 @@ internal static class CoShowIntroPatch _ = new LateTask(() => { Utils.CanCallMeetings = true; - }, 33f, "MeetingEnabled"); + }, 34f, "MeetingEnabled"); } - if (Options.Gamemode.GetValue() == 1 && Options.SNSChatInGameExtend.GetBool() && !Utils.isHideNSeek) + if (Options.Gamemode.GetValue() == 1 && !Utils.isHideNSeek) { - Utils.ModeratorChatCommand("Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by doing tasks or surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing everyone\n{Options.MisfiresToSuicide.GetInt()} wrong kill(s) = suicide", true); + if (Main.NormalOptions.roleOptions.TryGetRoleOptions(RoleTypes.GuardianAngel, out var GuardianAngelOptions)) + { + GuardianAngelOptions.ProtectionDurationSeconds = 60f; + } + OptionManager.SyncGameOptions(); + + if (Options.SNSChatInGameExtend.GetBool()) + { + Utils.ModeratorChatCommand("Shift and Seek:\n\nImpostors can only kill someone while shapeshifted as them\nMeetings & Reports = Off", $"Crewmates win by tasks/surviving {Options.CrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing\nWrong kills are blocked. Kill animations are off", true); + } + if (Options.SNSChatInGameExtend2.GetBool()) + { + _ = new LateTask(() => + { + Utils.ChatCommand(DestroyableSingleton.Instance.Chat, "Correctly killing a target will directly turn them into a ghost and leave no dead body", "", false); + }, 6.6f, "SNSChatInGameExtend2"); + } } if ((GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) == 0.01f)) @@ -59,7 +98,7 @@ internal static class CoShowIntroPatch { if (Options.PNSChatInGame.GetBool()) { - Main.NormalOptions.KillCooldown = 13f; + Main.NormalOptions.KillCooldown = 8f; OptionManager.SyncGameOptions(); _ = new LateTask(() => @@ -85,6 +124,8 @@ internal static class CoShowIntroPatch { Utils.ModeratorChatCommand($"Poof and Seek:\n\nImpostors can only move while vanished\n Meetings & Reports = Off\nVisibly moving {Options.BadMoveTimeToSuicide.GetInt()}s as Phantom = Suicide", $"Crewmates win by doing tasks or surviving {Options.PNSCrewAutoWinsGameAfter.GetInt()}s\nImpostors win by killing everyone", true); } + + NormalGameEndChecker.firstDeath = ""; } } diff --git a/Patches/JoinAndLeavePatches.cs b/Patches/JoinAndLeavePatches.cs index e82006c..727eea5 100644 --- a/Patches/JoinAndLeavePatches.cs +++ b/Patches/JoinAndLeavePatches.cs @@ -60,6 +60,13 @@ internal static class OnGameJoinedPatch Utils.MapVote(true, true); }, Options.TimeBeforeAllMapPoll.GetInt(), "AutoMapPoll"); } + if (Options.AutomaticModePoll.GetBool()) + { + new LateTask(() => + { + Utils.ModeVote(true); + }, Options.TimeBeforeModePoll.GetInt(), "AutoModePoll"); + } if (Options.Public.GetBool() && DisconnectManager.Rehosting) { @@ -114,7 +121,6 @@ class OnPlayerJoinedPatch } else { - if (!BanManager.TempBanWhiteList.Contains(Client.GetHashedPuid())) BanManager.TempBanWhiteList.Add(Client.GetHashedPuid()); AmongUsClient.Instance.KickPlayer(Client.Id, true); Logger.Info(Translator.Get("invalidFriendCodeBan", Client.PlayerName), "BanInvalidFriendCode"); Logger.SendInGame(Translator.Get("invalidFriendCodeBan", Client.PlayerName)); @@ -205,6 +211,40 @@ public static class ShowLobbyTimer_GameStartManager_Start_Postfix { if (__instance == null || !GameData.Instance || !AmongUsClient.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame || !AmongUsClient.Instance.AmHost) return; + // Toes who nose + //Main.NormalOptions.MaxPlayers = 100; + //OptionManager.SyncGameOptions(); HudManager.Instance.ShowLobbyTimer(600); } +} + +[HarmonyPatch(typeof(GameData), nameof(GameData.ShowNotification))] +public static class ShowNotificationPatch +{ + static bool Prefix(string playerName, DisconnectReasons reason) + { + switch (reason) + { + case DisconnectReasons.ExitGame: + Logger.Info($" {playerName} left the game. ({reason})", "ShowNotificationPatch"); + Logger.SendInGame($" {playerName} left the game. ({reason})"); + return false; + + case DisconnectReasons.Sanctions: + case DisconnectReasons.Hacking: + Logger.Info($" {playerName} was banned from the game. ({reason})", "ShowNotificationPatch"); + Logger.SendInGame($" {playerName} was banned from the game. ({reason})"); + return false; + + case DisconnectReasons.DuplicateConnectionDetected: + case DisconnectReasons.ClientTimeout: + Logger.Info($" {playerName} was kicked from the game. ({reason})", "ShowNotificationPatch"); + Logger.SendInGame($" {playerName} was kicked from the game. ({reason})"); + return false; + + default: + Logger.Info($" {playerName} disconnected from the game. ({reason})", "ShowNotificationPatch"); + return true; + } + } } \ No newline at end of file diff --git a/Patches/MeetingPatches.cs b/Patches/MeetingPatches.cs index 8dff034..3bb6c7e 100644 --- a/Patches/MeetingPatches.cs +++ b/Patches/MeetingPatches.cs @@ -70,7 +70,7 @@ class MeetingHudStartPatch PlayerControl player = null; foreach (var pc in PlayerControl.AllPlayerControls) { - if (pc.PlayerId == playerState.TargetPlayerId) + if (pc.PlayerId == playerState.PlayerId) { player = pc; break; @@ -89,7 +89,7 @@ class MeetingHudStartPatch taskTextMeeting.text = taskText; taskTextMeeting.gameObject.name = "TaskTextMeeting"; taskTextMeeting.enableWordWrapping = false; - taskTextMeeting.enabled = player.AmOwner || player.PlayerId == playerState.TargetPlayerId; + taskTextMeeting.enabled = player.AmOwner || player.PlayerId == playerState.PlayerId; } } diff --git a/Patches/NumberOptionsPatch.cs b/Patches/NumberOptionsPatch.cs index edb40f6..c573d61 100644 --- a/Patches/NumberOptionsPatch.cs +++ b/Patches/NumberOptionsPatch.cs @@ -126,6 +126,11 @@ public static class NumberOption_Initialize __instance.Increment = 0.05f; break; + case StringNames.JudgeTaskRequirement: + __instance.Increment = 1f; + __instance.ValidRange = new FloatRange(0f, 100f); + break; + default: __instance.Increment = 1f; break; diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index fa62007..36b4307 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -1,4 +1,5 @@ using AmongUs.GameOptions; +using BepInEx.Unity.IL2CPP.Utils.Collections; using System; using InnerNet; using UnityEngine; @@ -149,4 +150,116 @@ internal static class OnGameStartPatch PastStartScreen = true; } + + // All Patches below are for the speeded up Chat In Game. + // We force the complete intro to skip using 2 patches. + // Some PlayerData related things return null when you do this, so we have to set those all as well. + [HarmonyPatch(typeof(ShhhBehaviour), nameof(ShhhBehaviour.PlayAnimation))] + public static class ShhhBehaviourPlayAnimationPatch + { + static bool Prefix() + { + if (!AmongUsClient.Instance.AmHost) return true; + + if ((Options.SNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 1) || (Options.PNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 3)) + { + HudManager.Instance.shhhEmblem.gameObject.SetActive(false); + return false; + } + else return true; + } + } + + [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] + public static class IntroCutsceneCoBeginPatch + { + public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.IEnumerator __result) + { + if (!AmongUsClient.Instance.AmHost) return; + if ((Options.SNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 1) || (Options.PNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 3)) + { + __result = SkipIntro(__instance, __result).WrapToIl2Cpp(); + } + } + + private static System.Collections.IEnumerator SkipIntro(IntroCutscene i, Il2CppSystem.Collections.IEnumerator k) + { + int steps = 0; + + while (k.MoveNext() && steps++ < 1024) {} + + if (steps >= 1024) + { + if (i != null && i.gameObject != null) i.gameObject.SetActive(false); + } + yield break; + } + } + + private static NamePlateViewData meetingNameplate; + [HarmonyPatch(typeof(CosmeticsCache), nameof(CosmeticsCache.GetNameplate))] + public static class CosmeticsCacheGetNameplatePatch + { + public static bool Prefix(ref NamePlateViewData __result) + { + if (!AmongUsClient.Instance.AmHost) return true; + if ((Options.SNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 1) || (Options.PNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 3)) + { + if (MeetingHud.Instance == null) return true; + + if (meetingNameplate == null) + { + meetingNameplate = ScriptableObject.CreateInstance(); + meetingNameplate.Image = null; + } + + __result = meetingNameplate; + return false; + } + else return true; + } + } + + [HarmonyPatch(typeof(CosmeticsCache), nameof(CosmeticsCache.Destroy))] + public static class CosmeticsCacheDestroyPatch + { + public static void Postfix() + { + if (!AmongUsClient.Instance.AmHost) return; + + if (meetingNameplate != null) Object.Destroy(meetingNameplate); + meetingNameplate = null; + } + } + + [HarmonyPatch(typeof(ShapeshifterPanel), nameof(ShapeshifterPanel.SetPlayer))] + public static class ShapeshifterPanelSetPlayerPatch + { + public static bool Prefix(ShapeshifterPanel __instance, int index, NetworkedPlayerInfo playerInfo, Il2CppSystem.Action onShift) + { + if (__instance == null || playerInfo == null || !AmongUsClient.Instance.AmHost) return true; + + if ((Options.SNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 1) || (Options.PNSChatInGameFast.GetBool() && Options.Gamemode.GetValue() == 3)) + { + __instance.shapeshift = onShift; + __instance.PlayerIcon.SetFlipX(false); + __instance.PlayerIcon.ToggleName(false); + + SpriteRenderer[] componentsInChildren = __instance.GetComponentsInChildren(); + foreach (var spriteRenderer in componentsInChildren) + { + spriteRenderer.material.SetInt(PlayerMaterial.MaskLayer, index + 2); + } + + __instance.PlayerIcon.SetMaskLayer(index + 2); + __instance.PlayerIcon.UpdateFromEitherPlayerDataOrCache(playerInfo, PlayerOutfitType.Default, PlayerMaterial.MaskType.ComplexUI, false, null); + + __instance.NameText.text = playerInfo.PlayerName; + __instance.LevelNumberText.text = ProgressionManager.FormatVisualLevel(playerInfo.PlayerLevel); + + return false; + } + else return true; + } + } } \ No newline at end of file diff --git a/Patches/PlayerControlPatches.cs b/Patches/PlayerControlPatches.cs index 3eaa125..69b19a6 100644 --- a/Patches/PlayerControlPatches.cs +++ b/Patches/PlayerControlPatches.cs @@ -51,7 +51,7 @@ internal static class MurderPlayerPatch public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target, [HarmonyArgument(1)] MurderResultFlags resultFlags, ref bool __state) { - if (!AmongUsClient.Instance.AmHost || !resultFlags.HasFlag(MurderResultFlags.Succeeded)) return; + if (!AmongUsClient.Instance.AmHost) return; byte playerId = __instance.Data.PlayerId; @@ -60,11 +60,25 @@ internal static class MurderPlayerPatch killCount[playerId] = 0; } + if (resultFlags.HasFlag(MurderResultFlags.Succeeded)) + { + killCount[playerId]++; + Logger.Info($" {__instance.Data.PlayerName} killed {target.Data.PlayerName}", "MurderPlayer"); + if (string.IsNullOrEmpty(NormalGameEndChecker.firstDeath)) + { + NormalGameEndChecker.firstDeath = target.Data.PlayerName; + Utils.HasMurdered = true; + } - killCount[playerId]++; - Logger.Info($" {__instance.Data.PlayerName} killed {target.Data.PlayerName}", "MurderPlayer"); + if (Options.Gamemode.GetValue() == 1 && !Utils.isHideNSeek) + { + if (target.Data.PlayerId == __instance.shapeshiftTargetPlayerId) return; + Logger.Info($" {__instance.Data.PlayerName} directly killed {target.Data.PlayerName} in SnS, forcing suicide. (Are they hacking?)", "MurderPlayer"); + __instance.RpcSetRole(RoleTypes.ImpostorGhost); + } + } - if ((target == PlayerControl.LocalPlayer || PlayerControl.LocalPlayer.Data.IsDead) && !Main.DisableInfoWhenDead.Value) + if ((target == PlayerControl.LocalPlayer || PlayerControl.LocalPlayer.Data.IsDead) && !Main.DisableInfoWhenDead.Value && resultFlags.HasFlag(MurderResultFlags.Succeeded)) { foreach (var p in PlayerControl.AllPlayerControls) { @@ -82,30 +96,26 @@ internal static class MurderPlayerPatch //1 = Shift and Seek if (Options.Gamemode.GetValue() == 1 && !Utils.isHideNSeek) { - if (!resultFlags.HasFlag(MurderResultFlags.Succeeded)) return; - if (target.Data.PlayerId == __instance.shapeshiftTargetPlayerId) { + killCount[playerId]++; Logger.Info($" {__instance.Data.PlayerName} correctly killed {target.Data.PlayerName} ", "SNSKillManager"); + target.RpcSetRole(RoleTypes.CrewmateGhost); + if (string.IsNullOrEmpty(NormalGameEndChecker.firstDeath)) + { + NormalGameEndChecker.firstDeath = target.Data.PlayerName; + Utils.HasMurdered = true; + } } else { - if (!misfireCount.ContainsKey(playerId)) - misfireCount[playerId] = 0; - - misfireCount[playerId]++; - - if (misfireCount[__instance.Data.PlayerId] >= Options.MisfiresToSuicide.GetFloat()) - { - __instance.RpcSetRole(RoleTypes.ImpostorGhost); - Logger.Info($" {__instance.Data.PlayerName} misfired {misfireCount[playerId]} time(s) and suicided", "SNSKillManager"); - Logger.SendInGame($" {__instance.Data.PlayerName} misfired {misfireCount[playerId]} time(s) and suicided"); - } + Logger.Info($" {__instance.Data.PlayerName} misfired trying to kill {target.Data.PlayerName}. Blocking kill", "SNSKillManager"); } } } } +/* [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckShapeshift))] internal static class CheckShapeshiftPatch { @@ -123,6 +133,7 @@ internal static class CheckShapeshiftPatch else return true; } } +*/ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))] class PlayerControlCompleteTaskPatch diff --git a/README.md b/README.md index 30329f3..3603ca2 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,15 @@ **** ‎ -# 🎮 Features (v1.9.4): +# 🎮 Features (v2.0.0): ## ♠️ Client Side settings: -- Game Master (Spectator) -- Show FPS -- Dark Theme +- Toggle Game Master (Spectator) +- Toggle Show FPS +- Toggle Dark Theme - Toggle Lobby Music -- Show/Hide Info When Dead +- Toggle Info When Dead +- Toggle Command Helper ## ⚙️ Technical settings: - Advanced AFK Detection system - Kick/Ban players under/above a certain level @@ -53,7 +54,7 @@ - Banlist system - Denyword and Denyname system - Advanced Moderator system -- To add Admins, Moderators and VIP's, type the user's Friendcode in the designated files located in AUR-DATA +- To add Admins/Moderators/VIP's, type the user's Friendcode in the designated files located in AUR-DATA - Enable No Game End - Custom starting countdown ## ♻️ Automation settings: @@ -73,6 +74,7 @@ - % Tasks completed for Crewmates to win - Enable/Disable any task - All players have same tasks +- Shield player who died first - Hide and Seek: Custom Seeker count ## 🏆 Custom Gamemodes: - 0 Kill Cooldown -> Set Kill Cooldown to 0.01 @@ -92,18 +94,15 @@ - Lanuage support: English, Italian, Japanese, Russian, Simplified Chinese, Spanish - Select a language in Language/YourLanguage.txt - Preset system for mod settings -- Command Helper - Creatable and customizable Templates - Eht Dleks (Reverse Skeld) Map - Ability to cancel starting countdown - Improved main menu - Faster startup time - Zoom out in lobby or when dead -- See player kill/task count and Role when dead - Higher and more accurate Option ranges - Always visible lobby timer - Access experimental animation test scene -- Manually set multiple Seekers in HnS - 🎉 And this all works in public lobbies! ### Hotkeys @@ -142,6 +141,7 @@ | /clearseekers | Clears manual Seeker list in HnS | | /mapvote | Starts a vote for the map. Default maps only | | /allmapvote | Starts a vote for the map. Includes Eht Dleks | +| /gmvote | Starts a vote for the gamemode | ‎ **Credit to these mods and their developers for their code:** diff --git a/Resources/Language/English.json b/Resources/Language/English.json index c0690aa..38d6284 100644 --- a/Resources/Language/English.json +++ b/Resources/Language/English.json @@ -56,6 +56,7 @@ "allowFortegreen": "Allow Fortegreen Color", "noGameEnd": "No Game End", "mapVoteDuration": "Map Vote Duration", + "modeVoteDuration": "Gamemode Vote Duration", "tabGroupDeniedWords": "Denied Words", "enableDeniedWords": "Kick Players Who Say Denied Words", @@ -64,7 +65,7 @@ "slashColorCmd": "Who Can Use /color", "slashRolesAndGamemodeCmd": "Who Can Use /r & Templates", "slashLastGameCmd": "Who Can Use /l & /aur", - "slashMapAndSeekerCmd": "Who Can Use /seeker & /mapvote", + "slashMapAndSeekerCmd": "Who Can Use /seeker & Create Votes", "slashKickCmd": "Who Can Use /kick & /ckick", "slashBanCmd": "Who Can Use /ban & /cban", "slashEndMeetingCmd": "Who Can Use /endmeeting", @@ -83,6 +84,8 @@ "tabGroupSNS": "Shift and Seek", "snsChatInGame": "Chat during gameplay (experimental)", "snsChatInGameExtend": "Send gamemode info after game start", + "snsChatInGameExtendTwo": "Send extended info after game start", + "snsChatInGameFast": "Faster Meeting (experimental+)", "snsSettingsOverride": "Auto Update Settings", "cantKillTime": "After Misfiring, Can't kill for", "misfiresToSuicide": "Suicide After Amount Of Misfires", @@ -112,6 +115,8 @@ "disableCloseDoor": "Disable Door Sabotages", "tabGroupGameplayGeneral": "General", + "shieldFirstDeath": "Shield Player Who Died First", + "shieldFirstDeathDuration": "Shield Duration", "disableAnnoyingMeetingCalls": "Disable Meeting Calls First 30s", "disableReport": "Disable Body Reports", "changeDecontaminationTime": "Override Decontamination Time (Standard Only)", @@ -234,7 +239,7 @@ "disableSubmitScan": "Disable Submit Scan", "disableUploadData": "Disable Upload Data", - "allCommandsFull": "ALL COMMANDS:\n\n/help\nShow this message.\n/aur, /socials\nSend Among Us Revamped social links.\n\n/r, /gm\nSend current mode description.\n/l, /lastgame\nSend last winner info.\n\n/0kc, /0killcooldown\nSend 0 Kill Cooldown mode description.\n/sns, /shiftandseek\nSend Shift and Seek mode description.\n/pns, /poofandseek\nSend Poof and Seek mode description.\n/sp, /speedrun\nSend Speedrun mode description.\n\n/kick, /ban\nKick or ban a player by name.\n/ckick, /cban\nKick or ban a player by color.\n\n/vip /moderator /admin\nAdd a player as VIP, Moderator, or Admin.\n/removevip, /removemoderator, /removeadmin\nRemove VIP, Moderator, or Admin from a player.\n\n/eg, /endgame\nEnd the current game.\n/em, /endmeeting\nEnd the current meeting.\n/s, /start\nBegin the game starting countdown.\n\n/t {template}\nSend a template from AUR-DATA/templates.txt.\n\n/col, /color, /colour\nSet your color, can be used to bypass limits.\n\n/seeker {name}\nForce someone as Seeker in HnS.\n/seekers\nShow forced seeker list.\n/clearseeker\nClear forced seeker list.\n\n/mapvote\nStarts a map vote containing default maps.\n\n/allmapvote\nStarts a map vote that includes Eht Dleks.", + "allCommandsFull": "ALL COMMANDS:\n\n/help\nShow this message.\n/aur, /socials\nSend Among Us Revamped social links.\n\n/r, /gm\nSend current mode description.\n/l, /lastgame\nSend last winner info.\n\n/0kc, /0killcooldown\nSend 0 Kill Cooldown mode description.\n/sns, /shiftandseek\nSend Shift and Seek mode description.\n/sns2\nSend extended Shift and Seek mode description\n/pns, /poofandseek\nSend Poof and Seek mode description.\n/sp, /speedrun\nSend Speedrun mode description.\n\n/kick, /ban\nKick or ban a player by name.\n/ckick, /cban\nKick or ban a player by color.\n\n/vip /moderator /admin\nAdd a player as VIP, Moderator, or Admin.\n/removevip, /removemoderator, /removeadmin\nRemove VIP, Moderator, or Admin from a player.\n\n/eg, /endgame\nEnd the current game.\n/em, /endmeeting\nEnd the current meeting.\n/s, /start\nBegin the game starting countdown.\n\n/t {template}\nSend a template from AUR-DATA/templates.txt.\n\n/col, /color, /colour\nSet your color, can be used to bypass limits.\n\n/seeker {name}\nForce someone as Seeker in HnS.\n/seekers\nShow forced seeker list.\n/clearseeker\nClear forced seeker list.\n\n/mapvote\nStarts a map vote containing default maps.\n/allmapvote\nStarts a map vote that includes Eht Dleks.\n/gmvote\nStarts a gamemode vote.", "translationReloaded": "Your file has successfully been reset.\nTo apply all changes, restart your game.", @@ -262,6 +267,7 @@ "helpDesc": "Show all commands and what they do.", "snsDesc": "Send Shift and Seek gamemode Description.", + "snsTwoDesc": "Send Shift and Seek extended Description.", "0kcDesc": "Send 0 Kill Cooldown gamemode Description.", "SrDesc": "Send Speedrun gamemode Description.", "pnsDesc": "Send Poof and Seek gamemode Description.", @@ -291,6 +297,7 @@ "clearSeekerDesc": "Clears the manual Seeker list.", "mapVoteDesc": "Starts a vote between the 5 core maps.", "allMapVoteDesc": "Starts a vote between all 6 maps, including Eht Dleks.", + "modeVoteDesc": "Starts a vote between all gamemodes.", "banWordKick": "{0} was kicked because they sent a word in DeniedWords.txt {1} time(s)", "startWordKick": "{0} was kicked because they sent a word in StartWords.txt {1} time(s)", @@ -336,11 +343,12 @@ "automaticMapPoll": "Automatic /mapvote After Rejoining", "automaticAllMapPoll": "Automatic /allmapvote After Rejoining", - "timeBeforeMapPoll": "Time Before Poll Starts", + "timeBeforeMapPoll": "Time Before Vote Starts", + "automaticModePoll": "Automatic /gmvote After Rejoining", "pnsDisableSabotage": "Disable PnS Critical Sabotages", "pnsDisableCloseDoor": "Disable PnS Door Sabotages", "tabGroupPNS": "Poof and Seek", "badMoveTimeToSuicide": "Phantom Suicides After Visibly Moving For", - "snsImpostorCanVent": "Impostors can vent" + "snsImpostorCanVent": "Impostors Can Vent" } diff --git a/Resources/Language/Japanese.json b/Resources/Language/Japanese.json index 3bf42e9..ac0676e 100644 --- a/Resources/Language/Japanese.json +++ b/Resources/Language/Japanese.json @@ -336,10 +336,10 @@ "automaticMapPoll": "再参加後に /mapvote を自動実行", "automaticAllMapPoll": "再参加後に /allmapvote を自動実行", "timeBeforeMapPoll": "投票開始までの待機時間", - - "pnsDisableSabotage": "Disable PnS Critical Sabotages", - "pnsDisableCloseDoor": "Disable PnS Door Sabotages", - "tabGroupPNS": "Poof and Seek", - "badMoveTimeToSuicide": "Phantom Suicides After Visibly Moving For", - "snsImpostorCanVent": "Impostors can vent" + "pnsDesc": "Poof and Seekゲームモードの説明を送信します。", +"pnsDisableSabotage": "PnSの重大なサボタージュを無効にする", +"pnsDisableCloseDoor": "PnSのドア閉鎖サボタージュを無効にする", +"tabGroupPNS": "Poof and Seek", +"badMoveTimeToSuicide": "幻象師が明らかに移動した後、自殺するまでの時間", +"snsImpostorCanVent": "インポスターはベントを使用できる" } diff --git a/Resources/Language/Russian.json b/Resources/Language/Russian.json index d54e2e3..354e530 100644 --- a/Resources/Language/Russian.json +++ b/Resources/Language/Russian.json @@ -337,10 +337,10 @@ "automaticAllMapPoll": "Автоматический /allmapvote После Перезахода", "timeBeforeMapPoll": "Время До Начала Голосования За Карты", - "pnsDisableSabotage": "Disable PnS Critical Sabotages", - "pnsDisableCloseDoor": "Disable PnS Door Sabotages", - "tabGroupPNS": "Poof and Seek", - "badMoveTimeToSuicide": "Phantom Suicides After Visibly Moving For", - "snsImpostorCanVent": "Impostors can vent" - + "pnsDisableSabotage": "Выключить Критические Саботажи В Режиме Фантомные Прятки", + "pnsDisableCloseDoor": "Выключить Саботажи Дверей В Режиме Фантомные Прятки", + "tabGroupPNS": "Фантомные Прятки", + "badMoveTimeToSuicide": "Файнтомы Самоубиваются После Видимого Движения После", + "snsImpostorCanVent": "Предатели могут уходить в люк", + "pnsDesc": "Отправляет информацию о Режиме Фантомных Прятках." }